package com.regent.central.bill.web.sale_order;

import com.regent.central.bill.common.*;
import com.regent.central.bill.core.*;
import com.regent.central.bill.dto.*;
import com.regent.central.bill.dto.sale_order.*;
import com.regent.central.bill.service.*;
import com.regent.central.infrastructure.common.TimeKit;
import com.regent.central.infrastructure.common.Uid;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 销售单Controller
 *
 * @author 杨昌沛 646742615@qq.com
 * @date 2018-06-14 10:53
 **/
@Controller
@RequestMapping("/sale-orders")
public class SaleOrderController {

    @Resource
    private SaleOrderService saleOrderService;

    @Resource
    private CreateGoodsService createGoodsService;

    @Resource
    private ModifyGoodsService modifyGoodsService;

    @Resource
    private ReturnGoodsService returnGoodsService;

    @Resource
    private RemittanceService remittanceService;

    @Resource
    private ClearanceService clearanceService;

    @Resource
    private CreateGoodsRemarkService createGoodsRemarkService;

    @Resource
    private ReturnGoodsRemarkService returnGoodsRemarkService;

    @Resource
    private CreateGoodsSKUService createGoodsSKUService;

    @Resource
    private ModifyGoodsSKUService modifyGoodsSKUService;

    @Resource
    private ReturnGoodsSKUService returnGoodsSKUService;

    @Resource
    private SaleOrderActionService saleOrderActionService;

    @Resource
    private SaleOrderRemarkService saleOrderRemarkService;

    @Resource
    private OddmentLoggingService oddmentLoggingService;

    @Resource
    private ClearanceLoggingService clearanceLoggingService;

    @Resource
    private RemittanceLoggingService remittanceLoggingService;

    @Resource
    private SaleOrderActionLoggingService saleOrderActionLoggingService;

    @Resource
    private SaleOrderActionLoggingStyleService saleOrderActionLoggingStyleService;

    @Resource
    private SaleOrderActionLoggingStyleSKUService saleOrderActionLoggingStyleSKUService;

    @Resource
    private SaleOrderActionController saleOrderActionController;

    /**
     * 分页查询销售单列表
     *
     * @param request 查询请求
     * @return 分页的销售单列表回应结果
     */
    @RequestMapping(value = "", method = RequestMethod.GET)
    @ResponseBody
    public SaleOrderQueryResponse query(SaleOrderQueryRequest request) {
        SaleOrderQueryResponse response = new SaleOrderQueryResponse();
        List<SaleOrderQueryDTO> orders = saleOrderService.query(
                request.getUnitId(),
                request.getStartTime(),
                request.getEndTime(),
                request.getUserIds(),
                request.getTypes(),
                request.getSaleOrderStatus(),
                request.getReceiptStatus(),
                request.getStartSearchTime(),
                request.getPageNo(),
                request.getPageSize()
        );
        response.setOrders(orders);
        return response;
    }

    /**
     * 获取销售单详情接口
     *
     * @param request 销售单详情请求参数
     * @return 销售单详情回应结果
     */
    @RequestMapping(value = "/detail", method = RequestMethod.GET)
    @ResponseBody
    public SaleOrderDetailResponse detail(SaleOrderDetailRequest request) {
        SaleOrderDetailResponse response = new SaleOrderDetailResponse();

        SaleOrderDetailDTO detail = saleOrderService.detail(request.getSaleOrderId());
        if (detail == null) {
            response.setCode(404);
            response.setMessage("销售单不存在或已被删除!");
            return response;
        }
        response.setSaleOrderDetail(detail);

        return response;
    }

    /**
     * 获取销售单操作记录
     *
     * @param request 销售单操作记录请求
     * @return 销售单操作记录结果
     */
    @RequestMapping(value = "/actions", method = RequestMethod.GET)
    @ResponseBody
    public SaleOrderActionsResponse actions(SaleOrderActionsRequest request) {
        SaleOrderActionsResponse response = new SaleOrderActionsResponse();
        List<SaleOrderActionDTO> actions = saleOrderActionService.getPagedSaleOrderActions(
                request.getSaleOrderId(),
                request.getStartSearchTime(),
                request.getOrderBy(),
                request.getOrderAs(),
                request.getPageNo(),
                request.getPageSize()
        );
        response.setActions(actions);
        return response;
    }

    /**
     * 撤销销售单
     *
     * @param request 撤销销售单请求
     * @return 撤销销售单回应
     */
    @RequestMapping(value = "", method = RequestMethod.DELETE)
    @ResponseBody
    public SaleOrderCancelResponse cancel(SaleOrderCancelRequest request) {
        String saleOrderId = request.getSaleOrderId();
        List<SaleOrderAction> parents = saleOrderActionService.getSaleOrderAllActions(saleOrderId, true);
        // 排序 发货/改欠货/被改欠货动作需要放在要货之前撤销
        List<ActionType> order = Arrays.asList(
                ActionType.ODDMENT,
                ActionType.CLEARANCE_IN,
                ActionType.CLEARANCE_OUT,
                ActionType.REMITTANCE,
                ActionType.DELIVERY,
                ActionType.MODIFY_THAT,
                ActionType.MODIFY_THIS,
                ActionType.CREATE,
                ActionType.RETURN
        );
        parents.sort((a, b) -> {
            Integer x = order.indexOf(a.getActionType());
            Integer y = order.indexOf(b.getActionType());
            return x.compareTo(y);
        });
        return saleOrderService.doAtomically(() -> {
            parents.forEach(parent -> {
                SaleOrderActionCancelResponse response = null;
                SaleOrderActionCancelRequest cancel = new SaleOrderActionCancelRequest();
                cancel.setBossUnitId(request.getBossUnitId());
                cancel.setUnitId(request.getUnitId());
                cancel.setStorehouseId(request.getStorehouseId());
                cancel.setUserId(request.getUserId());
                cancel.setSaleOrderId(request.getSaleOrderId());
                cancel.setActionId(parent.getId());
                switch (parent.getActionType()) {
                    case REMITTANCE:
                        response = saleOrderActionController.cancelRemittance(cancel);
                        break;
                    case ODDMENT:
                        saleOrderActionService.cancel(parent.getId(), request.getUserId());
                        break;
                    case CREATE:
                        response = saleOrderActionController.cancelCreateGoods(cancel);
                        break;
                    case MODIFY_THAT:
                        response = saleOrderActionController.cancelModifyThatGoods(cancel);
                        break;
                    case MODIFY_THIS:
                        response = saleOrderActionController.cancelModifyThisGoods(cancel);
                        break;
                    case RETURN:
                        response = saleOrderActionController.cancelReturnGoods(cancel);
                        break;
                    case DELIVERY:
                        response = saleOrderActionController.cancelDelivery(cancel);
                        break;
                    case CLEARANCE_IN:
                        response = saleOrderActionController.cancelClearance(cancel);
                        break;
                    case CLEARANCE_OUT:
                        response = saleOrderActionController.cancelClearance(cancel);
                        break;
                }
                if (response != null && !response.isSuccess()) throw new IllegalStateException(response.getMessage());
            });
            saleOrderService.cancel(saleOrderId);

            return new SaleOrderCancelResponse();
        });
    }

    /**
     * 获取销售单抹零金额
     *
     * @param request 销售单抹零金额请求参数
     * @return 销售单抹零金额回应结果
     */
    @RequestMapping(value = "/oddment", method = RequestMethod.GET)
    @ResponseBody
    public SaleOrderOddmentResponse oddment(SaleOrderOddmentRequest request) {
        BigDecimal oddment = saleOrderService.getSaleOrderOddment(request.getSaleOrderId());
        SaleOrderOddmentResponse response = new SaleOrderOddmentResponse();
        response.setOddment(oddment);
        return response;
    }

    /**
     * 创建销售单
     *
     * @param request 创建销售单请求参数
     * @return 销售单创建返回结果
     */
    @RequestMapping(value = "", method = RequestMethod.POST)
    @ResponseBody
    public SaleOrderCreateResponse create(@RequestBody SaleOrderCreateRequest request) {
        String orderId = saleOrderService.doAtomically(() -> {
            // 保存销售单一级数据
            String saleOrderId = saveSaleOrder(request);
            // 随动更新 二级数据 : 本单应付
            saleOrderService.updateAmountExpected(saleOrderId);
            //总收退款金额	销售单详情	随动更新	∑（所有收/退款记录金额）
            saleOrderService.updateAmountArrived(saleOrderId);
            //总核进/出金额	销售单详情	随动更新	∑（核进/出金额）
            saleOrderService.updateAmountCleared(saleOrderId);
            //结欠	销售单详情	随动更新	∑（sku开单数×sku开单单价 ± sku改欠货数×sku开单单价 -sku 退货数×sku退货单价） - 抹零金额 + ∑核进金额 - ∑核出金额 - ∑收款账户收退款金额
            saleOrderService.updateAmountRemain(saleOrderId);
            //销售总数	销售单详情	随动更新	∑（sku开单数） ± ∑（sku改欠货数）
            saleOrderService.updateTotalSold(saleOrderId);
            // 开单总数	销售单详情	随动更新	∑（sku开单数）
            saleOrderService.updateTotalCreated(saleOrderId);
            // 改欠货总数	销售单详情	随动更新	∑（sku改欠货数）
            saleOrderService.updateTotalModified(saleOrderId);
            //退货总数	销售单详情	随动更新	∑（sku退货数）
            saleOrderService.updateTotalReturned(saleOrderId);
            //发货数	发货/销售单详情	随动更新	∑（发货sku）
            saleOrderService.updateTotalDelivered(saleOrderId);
            // 欠货数	销售单详情	随动更新	∑（sku开单数） ± ∑（sku被改欠货数） - ∑（发货sku）
            saleOrderService.updateTotalOwed(saleOrderId);
            //总开单金额	销售单详情	随动更新	∑(sku开单数×sku金额)
            saleOrderService.updateAmountCreated(saleOrderId);
            // 总改欠货金额	销售单详情	随动更新	∑(sku改欠货数×sku金额)
            saleOrderService.updateAmountModified(saleOrderId);
            // 总退货金额	销售单详情	随动更新	∑(sku退货数×sku金额)
            saleOrderService.updateAmountReturned(saleOrderId);
            // 返回ID
            return saleOrderId;
        });
        SaleOrderCreateResponse response = new SaleOrderCreateResponse(0, "操作成功");
        response.setSaleOrderId(orderId);
        return response;
    }

    private String saveSaleOrder(SaleOrderCreateRequest request) {
        SaleOrderCreateDTO order = request.getSaleOrderCreate();
        Date now = new Date();
        String unitID = request.getUnitId();
        String orderDay = TimeKit.format(now, "yyyy-MM-dd");

        Integer maxOrderNo = saleOrderService.getMaxOrderNoOfUnitIdAndOrderDay(unitID, orderDay);
        Integer orderNo = maxOrderNo == null ? 1 : maxOrderNo + 1;

        SaleOrder saleOrder = new SaleOrder();
        saleOrder.setId(Uid.random());
        saleOrder.setType(SaleOrderType.SALE_ORDER);
        saleOrder.setOrderDay(orderDay);
        saleOrder.setOrderNo(orderNo);
        saleOrder.setCustomerId(order.getCustomerId());
        saleOrder.setAddressId(order.getAddressId());
        saleOrder.setBossUnitId(request.getBossUnitId());
        saleOrder.setUnitId(request.getUnitId());
        saleOrder.setCanceled(false);
        saleOrder.setDeliveryOnScene(order.isDeliveryOnScene());
        saleOrder.setOddment(order.getOddment() != null ? order.getOddment() : BigDecimal.ZERO);
        saleOrder.setMerchandiserId(request.getUserId());
        saleOrder.setUserCreated(request.getUserId());
        saleOrder.setUserOrdered(request.getUserId());
        saleOrder.setDateOrdered(now);

        // 增加抹零操作记录
        SaleOrderAction action = new SaleOrderAction();
        action.setId(Uid.random());
        action.setUserCreated(request.getUserId());
        action.setCentralBillSaleOrderId(saleOrder.getId());
        action.setActionType(ActionType.ODDMENT);
        action.setActionId(saleOrder.getId());
        saleOrderActionService.insertSelective(action);

        // 增加抹零变动记录
        OddmentLogging logging = new OddmentLogging();
        logging.setId(Uid.random());
        logging.setCentralBillSaleOrderId(saleOrder.getId());
        logging.setType(LoggingType.CREATE);
        logging.setOldAmount(BigDecimal.ZERO);
        logging.setNewAmount(saleOrder.getOddment());
        logging.setUserCreated(request.getUserId());
        oddmentLoggingService.insertSelective(logging);

        saleOrderService.insertSelective(saleOrder);
        saveRemittances(request, saleOrder.getId());
        saveClearances(request, saleOrder.getId());
        saveCreateGoods(request, saleOrder.getId());
        saveModifyGoods(request, saleOrder.getId());
        saveReturnGoods(request, saleOrder.getId());
        saveRemarks(request, saleOrder.getId());

        return saleOrder.getId();
    }

    private void saveRemarks(SaleOrderCreateRequest request, String saleOrderId) {
        SaleOrderCreateDTO order = request.getSaleOrderCreate();
        List<SaleOrderRemarkDTO> remarks = order.getRemarks() != null ? order.getRemarks() : Collections.emptyList();
        Date now = new Date();
        AtomicInteger sequence = new AtomicInteger(0);
        remarks.forEach(dto -> {
            SaleOrderRemark remark = new SaleOrderRemark();
            remark.setId(Uid.random());
            remark.setRemark(dto.getRemark());
            remark.setDateRemarked(StringUtils.isEmpty(dto.getDateRemarked()) ? now : TimeKit.parse(dto.getDateRemarked(), "yyyy-MM-dd HH:mm:ss"));
            remark.setCentralBillSaleOrderId(saleOrderId);
            remark.setSequence(sequence.getAndIncrement());
            remark.setUserCreated(request.getUserId());
            saleOrderRemarkService.insertSelective(remark);
        });
    }

    private void saveClearances(SaleOrderCreateRequest request, String saleOrderId) {
        SaleOrderCreateDTO order = request.getSaleOrderCreate();
        List<ClearanceDTO> clearances = order.getClearances();
        if (clearances == null || clearances.isEmpty()) return;
        clearances.forEach(dto -> {
            save(request, saleOrderId, dto);
        });
    }

    private void save(SaleOrderCreateRequest request, String saleOrderId, ClearanceDTO dto) {
        Clearance clearance = new Clearance();
        clearance.setId(Uid.random());
        clearance.setBossUnitId(request.getBossUnitId());
        clearance.setUnitId(request.getUnitId());
        clearance.setClearanceType(dto.getClearanceType());
        clearance.setAmount(dto.getAmount());
        clearance.setUserCreated(request.getUserId());
        clearanceService.insertSelective(clearance);

        // 变动记录
        ClearanceLogging logging = new ClearanceLogging();
        logging.setId(Uid.random());
        logging.setCentralActionClearanceId(clearance.getId());
        logging.setType(LoggingType.CREATE);
        logging.setOldAmount(BigDecimal.ZERO);
        logging.setNewAmount(clearance.getAmount());
        logging.setUserCreated(request.getUserId());
        clearanceLoggingService.insertSelective(logging);

        SaleOrderAction self = new SaleOrderAction();
        self.setId(Uid.random());
        self.setCentralBillSaleOrderId(saleOrderId);
        self.setActionType(dto.getClearanceType() == ClearanceType.IN ? ActionType.CLEARANCE_IN : ActionType.CLEARANCE_OUT);
        self.setActionId(clearance.getId());
        self.setUserCreated(request.getUserId());
        saleOrderActionService.insertSelective(self);

        SaleOrderAction that = new SaleOrderAction();
        that.setId(Uid.random());
        that.setCentralBillSaleOrderId(dto.getSaleOrderId());
        that.setActionType(dto.getClearanceType() == ClearanceType.IN ? ActionType.CLEARANCE_OUT : ActionType.CLEARANCE_IN);
        that.setActionId(clearance.getId());
        that.setUserCreated(request.getUserId());
        saleOrderActionService.insertSelective(that);
    }

    private void saveRemittances(SaleOrderCreateRequest request, String saleOrderId) {
        Date now = new Date();
        SaleOrderCreateDTO order = request.getSaleOrderCreate();
        List<RemittanceDTO> ins = order.getInRemittances() != null ? order.getInRemittances() : Collections.emptyList();
        ins.stream().peek(in -> in.setRemittanceType(RemittanceType.RECEIPT)).forEach(in -> save(request, saleOrderId, now, order, in));
        List<RemittanceDTO> outs = order.getOutRemittances() != null ? order.getOutRemittances() : Collections.emptyList();
        outs.stream().peek(out -> out.setRemittanceType(RemittanceType.REFUND)).forEach(out -> save(request, saleOrderId, now, order, out));
    }

    private void save(SaleOrderCreateRequest request, String saleOrderId, Date now, SaleOrderCreateDTO order, RemittanceDTO dto) {
        Remittance remittance = new Remittance();
        remittance.setId(Uid.random());
        remittance.setBossUnitId(request.getBossUnitId());
        remittance.setUnitId(request.getUnitId());
        remittance.setUserCreated(request.getUserId());
        remittance.setType(dto.getRemittanceType());
        remittance.setRemitMethodId(dto.getRemitMethodId());
        remittance.setSource(BillType.SALE_ORDER);
        remittance.setAmount(dto.getAmount());
        remittance.setCustomerId(order.getCustomerId());
        remittanceService.insertSelective(remittance);

        // 变动记录
        RemittanceLogging logging = new RemittanceLogging();
        logging.setId(Uid.random());
        logging.setCentralActionRemittanceId(remittance.getId());
        logging.setType(LoggingType.CREATE);
        logging.setOldAmount(remittance.getAmount());
        logging.setNewAmount(remittance.getAmount());
        logging.setOldRemitMethodId(remittance.getRemitMethodId());
        logging.setNewRemitMethodId(remittance.getRemitMethodId());
        logging.setOldDateOperated(remittance.getDateOperated() != null ? remittance.getDateOperated() : now);
        logging.setNewDateOperated(remittance.getDateOperated() != null ? remittance.getDateOperated() : now);
        logging.setUserCreated(request.getUserId());
        remittanceLoggingService.insertSelective(logging);

        SaleOrderAction action = new SaleOrderAction();
        action.setId(Uid.random());
        action.setUserCreated(request.getUserId());
        action.setCentralBillSaleOrderId(saleOrderId);
        action.setActionType(ActionType.REMITTANCE);
        action.setActionId(remittance.getId());
        saleOrderActionService.insertSelective(action);
    }

    private void saveCreateGoods(SaleOrderCreateRequest request, String saleOrderId) {
        SaleOrderCreateDTO order = request.getSaleOrderCreate();
        List<SaleOrderCreateGoodsDTO> createGoodsDTOs = order.getCreateGoods();
        if (createGoodsDTOs == null || createGoodsDTOs.isEmpty()) return;

        SaleOrderAction parent = new SaleOrderAction();
        parent.setId(Uid.random());
        parent.setUserCreated(request.getUserId());
        parent.setCentralBillSaleOrderId(saleOrderId);
        parent.setActionType(ActionType.CREATE);
        parent.setActionId(saleOrderId);
        saleOrderActionService.insertSelective(parent);

        SaleOrderActionLogging logging = new SaleOrderActionLogging();
        logging.setId(Uid.random());
        logging.setCentralBillSaleOrderActionId(parent.getId());
        logging.setType(LoggingType.CREATE);
        logging.setUserCreated(request.getUserId());
        BigDecimal total = createGoodsDTOs.stream()
                .map(good -> good.getSkus().stream()
                        .map(CreateGoodsSKUDTO::getQuantity)
                        .reduce(BigDecimal::add)
                        .orElse(BigDecimal.ZERO)
                )
                .reduce(BigDecimal::add)
                .orElse(BigDecimal.ZERO);
        logging.setTotal(total);
        BigDecimal amount = createGoodsDTOs.stream()
                .map(good -> good.getSkus().stream()
                        .map(sku -> sku.getPrice().multiply(sku.getQuantity()))
                        .reduce(BigDecimal::add)
                        .orElse(BigDecimal.ZERO)
                )
                .reduce(BigDecimal::add)
                .orElse(BigDecimal.ZERO);
        logging.setAmount(amount);
        saleOrderActionLoggingService.insertSelective(logging);
        for (SaleOrderCreateGoodsDTO createGoodsDTO : createGoodsDTOs) {
            save(request, saleOrderId, parent, logging, createGoodsDTO);
        }
    }

    private void save(SaleOrderCreateRequest request, String saleOrderId, SaleOrderAction parent, SaleOrderActionLogging logging, SaleOrderCreateGoodsDTO createGoodsDTO) {
        CreateGoods goods = new CreateGoods();
        goods.setId(Uid.random());
        goods.setBossUnitId(request.getBossUnitId());
        goods.setUnitId(request.getUnitId());
        goods.setUserCreated(request.getUserId());
        goods.setGoodsId(createGoodsDTO.getGoodsId());
        goods.setStyleId(createGoodsDTO.getStyleId());
        goods.setCouponType(createGoodsDTO.getCouponType());
        goods.setPrice(createGoodsDTO.getPrice());
        goods.setPriceChanged(createGoodsDTO.isPriceChanged());
        goods.setDiscount(createGoodsDTO.getDiscount());
        goods.setCouponTemplateId(createGoodsDTO.getCouponTemplateId());
        createGoodsService.insertSelective(goods);

        SaleOrderActionLoggingStyle loggingStyle = new SaleOrderActionLoggingStyle();
        loggingStyle.setId(Uid.random());
        loggingStyle.setCentralBillSaleOrderActionLoggingId(logging.getId());
        loggingStyle.setStyleId(goods.getStyleId());
        loggingStyle.setGoodsId(goods.getGoodsId());
        BigDecimal total = createGoodsDTO.getSkus().stream()
                .map(CreateGoodsSKUDTO::getQuantity)
                .reduce(BigDecimal::add)
                .orElse(BigDecimal.ZERO);
        loggingStyle.setTotal(total);
        BigDecimal amount = createGoodsDTO.getSkus().stream()
                .map(sku -> sku.getPrice().multiply(sku.getQuantity()))
                .reduce(BigDecimal::add)
                .orElse(BigDecimal.ZERO);
        loggingStyle.setAmount(amount);
        loggingStyle.setUserCreated(request.getUserId());
        saleOrderActionLoggingStyleService.insertSelective(loggingStyle);

        SaleOrderAction action = new SaleOrderAction();
        action.setId(Uid.random());
        action.setParentId(parent.getId());
        action.setUserCreated(request.getUserId());
        action.setCentralBillSaleOrderId(saleOrderId);
        action.setActionType(ActionType.CREATE_GOODS);
        action.setActionId(goods.getId());
        saleOrderActionService.insertSelective(action);

        List<CreateGoodsSKUDTO> skus = createGoodsDTO.getSkus();
        saveCreateGoodsSkus(request, goods.getId(), loggingStyle, skus);

        List<RemarkDTO> remarks = createGoodsDTO.getRemarks();
        saveCreateGoodsRemarks(request, goods.getId(), remarks);
    }

    private void saveCreateGoodsSkus(SaleOrderCreateRequest request, String createGoodsId, SaleOrderActionLoggingStyle loggingStyle, List<CreateGoodsSKUDTO> skus) {
        if (skus == null || skus.isEmpty()) return;

        for (CreateGoodsSKUDTO dto : skus) {
            CreateGoodsSKU sku = new CreateGoodsSKU();
            sku.setId(Uid.random());
            sku.setCentralActionCreateGoodsId(createGoodsId);
            sku.setSkuId(dto.getSkuId());
            sku.setQuantity(dto.getQuantity());
            sku.setPrice(dto.getPrice());
            sku.setUserCreated(request.getUserId());
            createGoodsSKUService.insertSelective(sku);

            SaleOrderActionLoggingStyleSKU loggingStyleSKU = new SaleOrderActionLoggingStyleSKU();
            loggingStyleSKU.setId(Uid.random());
            loggingStyleSKU.setCentralBillSaleOrderActionLoggingStyleId(loggingStyle.getId());
            loggingStyleSKU.setSkuId(dto.getSkuId());
            loggingStyleSKU.setOldQuantity(BigDecimal.ZERO);
            loggingStyleSKU.setNewQuantity(dto.getQuantity());
            loggingStyleSKU.setOldPrice(BigDecimal.ZERO);
            loggingStyleSKU.setNewPrice(dto.getPrice());
            loggingStyleSKU.setUserCreated(request.getUserId());
            saleOrderActionLoggingStyleSKUService.insertSelective(loggingStyleSKU);
        }
    }

    private void saveCreateGoodsRemarks(SaleOrderCreateRequest request, String createGoodsId, List<RemarkDTO> remarks) {
        if (remarks == null || remarks.isEmpty()) return;
        int sequence = 0;
        for (RemarkDTO dto : remarks) {
            CreateGoodsRemark remark = new CreateGoodsRemark();
            remark.setId(Uid.random());
            remark.setBossUnitId(request.getBossUnitId());
            remark.setUnitId(request.getUnitId());
            remark.setUserCreated(request.getUserId());
            remark.setCentralActionCreateGoodsId(createGoodsId);
            remark.setRemark(dto.getRemark());
            remark.setSequence(sequence++);
            createGoodsRemarkService.insertSelective(remark);
        }
    }

    private void saveModifyGoods(SaleOrderCreateRequest request, String saleOrderId) {
        SaleOrderCreateDTO order = request.getSaleOrderCreate();
        List<SaleOrderModifyGoodsDTO> modifyGoodsDTOS = order.getModifyGoods();
        if (modifyGoodsDTOS == null || modifyGoodsDTOS.isEmpty()) return;

        Map<String, SaleOrderAction> modifyThatParents = new HashMap<>();
        Map<String, SaleOrderActionLogging> modifyThatLoggings = new HashMap<>();
        Map<String, SaleOrderAction> modifyThisParents = new HashMap<>();
        Map<String, SaleOrderActionLogging> modifyThisLoggings = new HashMap<>();

        for (SaleOrderModifyGoodsDTO modifyGoodsDTO : modifyGoodsDTOS) {
            save(request, saleOrderId, modifyGoodsDTOS, modifyThatParents, modifyThatLoggings, modifyThisParents, modifyThisLoggings, modifyGoodsDTO);
        }
    }

    private void save(SaleOrderCreateRequest request, String saleOrderId, List<SaleOrderModifyGoodsDTO> modifyGoodsDTOS, Map<String, SaleOrderAction> modifyThatParents, Map<String, SaleOrderActionLogging> modifyThatLoggings, Map<String, SaleOrderAction> modifyThisParents, Map<String, SaleOrderActionLogging> modifyThisLoggings, SaleOrderModifyGoodsDTO modifyGoodsDTO) {
        ModifyGoods goods = new ModifyGoods();
        goods.setId(Uid.random());
        goods.setBossUnitId(request.getBossUnitId());
        goods.setUnitId(request.getUnitId());
        goods.setUserCreated(request.getUserId());
        goods.setGoodsId(modifyGoodsDTO.getGoodsId());
        goods.setStyleId(modifyGoodsDTO.getStyleId());
        modifyGoodsService.insertSelective(goods);

        String modifiedSaleOrderId = modifyGoodsDTO.getSaleOrderId();
        SaleOrderActionLoggingStyle thatLoggingStyle;
        SaleOrderActionLoggingStyle thisLoggingStyle;
        // 改他单
        {
            SaleOrderAction parent = modifyThatParents.get(modifiedSaleOrderId);
            SaleOrderActionLogging logging = modifyThatLoggings.get(modifiedSaleOrderId);
            if (parent == null || logging == null) {
                modifyThatParents.put(modifiedSaleOrderId, parent = new SaleOrderAction());
                parent.setId(Uid.random());
                parent.setUserCreated(request.getUserId());
                parent.setCentralBillSaleOrderId(saleOrderId);
                parent.setActionType(ActionType.MODIFY_THAT);
                parent.setActionId(modifiedSaleOrderId);
                saleOrderActionService.insertSelective(parent);

                modifyThatLoggings.put(modifiedSaleOrderId, logging = new SaleOrderActionLogging());
                logging.setId(Uid.random());
                logging.setCentralBillSaleOrderActionId(parent.getId());
                logging.setType(LoggingType.CREATE);
                logging.setUserCreated(request.getUserId());
                BigDecimal totalInc = modifyGoodsDTOS.stream()
                        .map(good -> good.getSkus().stream()
                                .filter(sku -> sku.getQuantity().compareTo(BigDecimal.ZERO) > 0)
                                .map(ModifyGoodsSKUDTO::getQuantity)
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO)
                        )
                        .reduce(BigDecimal::add)
                        .orElse(BigDecimal.ZERO);
                logging.setTotalInc(totalInc);
                BigDecimal totalDec = modifyGoodsDTOS.stream()
                        .map(good -> good.getSkus().stream()
                                .filter(sku -> sku.getQuantity().compareTo(BigDecimal.ZERO) < 0)
                                .map(ModifyGoodsSKUDTO::getQuantity)
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO)
                        )
                        .reduce(BigDecimal::add)
                        .orElse(BigDecimal.ZERO);
                logging.setTotalDec(totalDec);
                logging.setTotal(totalInc.add(totalDec));

                BigDecimal amountInc = modifyGoodsDTOS.stream()
                        .map(good -> good.getSkus().stream()
                                .filter(sku -> sku.getPrice().multiply(sku.getQuantity()).compareTo(BigDecimal.ZERO) > 0)
                                .map(sku -> sku.getPrice().multiply(sku.getQuantity()))
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO)
                        )
                        .reduce(BigDecimal::add)
                        .orElse(BigDecimal.ZERO);
                logging.setAmountInc(amountInc);
                BigDecimal amountDec = modifyGoodsDTOS.stream()
                        .map(good -> good.getSkus().stream()
                                .filter(sku -> sku.getPrice().multiply(sku.getQuantity()).compareTo(BigDecimal.ZERO) < 0)
                                .map(sku -> sku.getPrice().multiply(sku.getQuantity()))
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO)
                        )
                        .reduce(BigDecimal::add)
                        .orElse(BigDecimal.ZERO);
                logging.setAmountDec(amountDec);
                logging.setAmount(amountInc.add(amountDec));
                saleOrderActionLoggingService.insertSelective(logging);
            }

            thatLoggingStyle = new SaleOrderActionLoggingStyle();
            thatLoggingStyle.setId(Uid.random());
            thatLoggingStyle.setCentralBillSaleOrderActionLoggingId(logging.getId());
            thatLoggingStyle.setStyleId(goods.getStyleId());
            thatLoggingStyle.setGoodsId(goods.getGoodsId());
            BigDecimal totalInc = modifyGoodsDTO.getSkus().stream()
                    .filter(sku -> sku.getQuantity().compareTo(BigDecimal.ZERO) > 0)
                    .map(ModifyGoodsSKUDTO::getQuantity)
                    .reduce(BigDecimal::add)
                    .orElse(BigDecimal.ZERO);
            thatLoggingStyle.setTotalInc(totalInc);
            BigDecimal totalDec = modifyGoodsDTO.getSkus().stream()
                    .filter(sku -> sku.getQuantity().compareTo(BigDecimal.ZERO) < 0)
                    .map(ModifyGoodsSKUDTO::getQuantity)
                    .reduce(BigDecimal::add)
                    .orElse(BigDecimal.ZERO);
            thatLoggingStyle.setTotalDec(totalDec);
            thatLoggingStyle.setTotal(totalInc.add(totalDec));
            BigDecimal amountInc = modifyGoodsDTO.getSkus().stream()
                    .filter(sku -> sku.getPrice().multiply(sku.getQuantity()).compareTo(BigDecimal.ZERO) > 0)
                    .map(sku -> sku.getPrice().multiply(sku.getQuantity()))
                    .reduce(BigDecimal::add)
                    .orElse(BigDecimal.ZERO);
            thatLoggingStyle.setAmountInc(amountInc);
            BigDecimal amountDec = modifyGoodsDTO.getSkus().stream()
                    .filter(sku -> sku.getPrice().multiply(sku.getQuantity()).compareTo(BigDecimal.ZERO) < 0)
                    .map(sku -> sku.getPrice().multiply(sku.getQuantity()))
                    .reduce(BigDecimal::add)
                    .orElse(BigDecimal.ZERO);
            thatLoggingStyle.setAmountDec(amountDec);
            thatLoggingStyle.setAmount(amountInc.add(amountDec));
            thatLoggingStyle.setUserCreated(request.getUserId());
            saleOrderActionLoggingStyleService.insertSelective(thatLoggingStyle);

            SaleOrderAction action = new SaleOrderAction();
            action.setId(Uid.random());
            action.setParentId(parent.getId());
            action.setUserCreated(request.getUserId());
            action.setCentralBillSaleOrderId(saleOrderId);
            action.setActionType(ActionType.MODIFY_THAT_GOODS);
            action.setActionId(goods.getId());
            saleOrderActionService.insertSelective(action);
        }

        // 改本单
        {
            SaleOrderAction parent = modifyThisParents.get(modifiedSaleOrderId);
            SaleOrderActionLogging logging = modifyThisLoggings.get(modifiedSaleOrderId);
            if (parent == null || logging == null) {
                modifyThisParents.put(modifiedSaleOrderId, parent = new SaleOrderAction());
                parent.setId(Uid.random());
                parent.setUserCreated(request.getUserId());
                parent.setCentralBillSaleOrderId(modifiedSaleOrderId);
                parent.setActionType(ActionType.MODIFY_THIS);
                parent.setActionId(saleOrderId);
                saleOrderActionService.insertSelective(parent);

                modifyThisLoggings.put(modifiedSaleOrderId, logging = new SaleOrderActionLogging());
                logging.setId(Uid.random());
                logging.setCentralBillSaleOrderActionId(parent.getId());
                logging.setType(LoggingType.CREATE);
                logging.setUserCreated(request.getUserId());
                BigDecimal totalInc = modifyGoodsDTOS.stream()
                        .map(good -> good.getSkus().stream()
                                .filter(sku -> sku.getQuantity().compareTo(BigDecimal.ZERO) > 0)
                                .map(ModifyGoodsSKUDTO::getQuantity)
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO)
                        )
                        .reduce(BigDecimal::add)
                        .orElse(BigDecimal.ZERO);
                logging.setTotalInc(totalInc);
                BigDecimal totalDec = modifyGoodsDTOS.stream()
                        .map(good -> good.getSkus().stream()
                                .filter(sku -> sku.getQuantity().compareTo(BigDecimal.ZERO) < 0)
                                .map(ModifyGoodsSKUDTO::getQuantity)
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO)
                        )
                        .reduce(BigDecimal::add)
                        .orElse(BigDecimal.ZERO);
                logging.setTotalDec(totalDec);
                logging.setTotal(totalInc.add(totalDec));

                BigDecimal amountInc = modifyGoodsDTOS.stream()
                        .map(good -> good.getSkus().stream()
                                .filter(sku -> sku.getPrice().multiply(sku.getQuantity()).compareTo(BigDecimal.ZERO) > 0)
                                .map(sku -> sku.getPrice().multiply(sku.getQuantity()))
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO)
                        )
                        .reduce(BigDecimal::add)
                        .orElse(BigDecimal.ZERO);
                logging.setAmountInc(amountInc);
                BigDecimal amountDec = modifyGoodsDTOS.stream()
                        .map(good -> good.getSkus().stream()
                                .filter(sku -> sku.getPrice().multiply(sku.getQuantity()).compareTo(BigDecimal.ZERO) < 0)
                                .map(sku -> sku.getPrice().multiply(sku.getQuantity()))
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO)
                        )
                        .reduce(BigDecimal::add)
                        .orElse(BigDecimal.ZERO);
                logging.setAmountDec(amountDec);
                logging.setAmount(amountInc.add(amountDec));
                saleOrderActionLoggingService.insertSelective(logging);
            }

            thisLoggingStyle = new SaleOrderActionLoggingStyle();
            thisLoggingStyle.setId(Uid.random());
            thisLoggingStyle.setCentralBillSaleOrderActionLoggingId(logging.getId());
            thisLoggingStyle.setStyleId(goods.getStyleId());
            thisLoggingStyle.setGoodsId(goods.getGoodsId());
            BigDecimal totalInc = modifyGoodsDTO.getSkus().stream()
                    .filter(sku -> sku.getQuantity().compareTo(BigDecimal.ZERO) > 0)
                    .map(ModifyGoodsSKUDTO::getQuantity)
                    .reduce(BigDecimal::add)
                    .orElse(BigDecimal.ZERO);
            thisLoggingStyle.setTotalInc(totalInc);
            BigDecimal totalDec = modifyGoodsDTO.getSkus().stream()
                    .filter(sku -> sku.getQuantity().compareTo(BigDecimal.ZERO) < 0)
                    .map(ModifyGoodsSKUDTO::getQuantity)
                    .reduce(BigDecimal::add)
                    .orElse(BigDecimal.ZERO);
            thisLoggingStyle.setTotalDec(totalDec);
            thisLoggingStyle.setTotal(totalInc.add(totalDec));
            BigDecimal amountInc = modifyGoodsDTO.getSkus().stream()
                    .filter(sku -> sku.getPrice().multiply(sku.getQuantity()).compareTo(BigDecimal.ZERO) > 0)
                    .map(sku -> sku.getPrice().multiply(sku.getQuantity()))
                    .reduce(BigDecimal::add)
                    .orElse(BigDecimal.ZERO);
            thisLoggingStyle.setAmountInc(amountInc);
            BigDecimal amountDec = modifyGoodsDTO.getSkus().stream()
                    .filter(sku -> sku.getPrice().multiply(sku.getQuantity()).compareTo(BigDecimal.ZERO) < 0)
                    .map(sku -> sku.getPrice().multiply(sku.getQuantity()))
                    .reduce(BigDecimal::add)
                    .orElse(BigDecimal.ZERO);
            thisLoggingStyle.setAmountDec(amountDec);
            thisLoggingStyle.setAmount(amountInc.add(amountDec));
            thisLoggingStyle.setUserCreated(request.getUserId());
            saleOrderActionLoggingStyleService.insertSelective(thisLoggingStyle);

            SaleOrderAction action = new SaleOrderAction();
            action.setId(Uid.random());
            action.setParentId(parent.getId());
            action.setUserCreated(request.getUserId());
            action.setCentralBillSaleOrderId(modifiedSaleOrderId);
            action.setActionType(ActionType.MODIFY_THIS_GOODS);
            action.setActionId(goods.getId());
            saleOrderActionService.insertSelective(action);
        }

        List<ModifyGoodsSKUDTO> skus = modifyGoodsDTO.getSkus();
        saveModifyGoodsSkus(request, goods.getId(), thatLoggingStyle, thisLoggingStyle, skus);
    }

    private void saveModifyGoodsSkus(SaleOrderCreateRequest request, String modifyGoodsId, SaleOrderActionLoggingStyle thatLoggingStyle, SaleOrderActionLoggingStyle thisLoggingStyle, List<ModifyGoodsSKUDTO> skus) {
        if (skus == null || skus.isEmpty()) return;

        for (ModifyGoodsSKUDTO dto : skus) {
            ModifyGoodsSKU sku = new ModifyGoodsSKU();
            sku.setId(Uid.random());
            sku.setCentralActionModifyGoodsId(modifyGoodsId);
            sku.setSkuId(dto.getSkuId());
            sku.setQuantity(dto.getQuantity());
            sku.setPrice(dto.getPrice());
            sku.setUserCreated(request.getUserId());
            modifyGoodsSKUService.insertSelective(sku);

            SaleOrderActionLoggingStyleSKU thatLoggingStyleSKU = new SaleOrderActionLoggingStyleSKU();
            thatLoggingStyleSKU.setId(Uid.random());
            thatLoggingStyleSKU.setCentralBillSaleOrderActionLoggingStyleId(thatLoggingStyle.getId());
            thatLoggingStyleSKU.setSkuId(dto.getSkuId());
            thatLoggingStyleSKU.setOldQuantity(BigDecimal.ZERO);
            thatLoggingStyleSKU.setNewQuantity(dto.getQuantity());
            thatLoggingStyleSKU.setOldPrice(BigDecimal.ZERO);
            thatLoggingStyleSKU.setNewPrice(dto.getPrice());
            thatLoggingStyleSKU.setUserCreated(request.getUserId());
            saleOrderActionLoggingStyleSKUService.insertSelective(thatLoggingStyleSKU);

            SaleOrderActionLoggingStyleSKU thisLoggingStyleSKU = new SaleOrderActionLoggingStyleSKU();
            thisLoggingStyleSKU.setId(Uid.random());
            thisLoggingStyleSKU.setCentralBillSaleOrderActionLoggingStyleId(thisLoggingStyle.getId());
            thisLoggingStyleSKU.setSkuId(dto.getSkuId());
            thisLoggingStyleSKU.setOldQuantity(BigDecimal.ZERO);
            thisLoggingStyleSKU.setNewQuantity(dto.getQuantity());
            thisLoggingStyleSKU.setOldPrice(BigDecimal.ZERO);
            thisLoggingStyleSKU.setNewPrice(dto.getPrice());
            thisLoggingStyleSKU.setUserCreated(request.getUserId());
            saleOrderActionLoggingStyleSKUService.insertSelective(thisLoggingStyleSKU);
        }
    }

    private void saveReturnGoods(SaleOrderCreateRequest request, String saleOrderId) {
        SaleOrderCreateDTO order = request.getSaleOrderCreate();
        List<SaleOrderReturnGoodsDTO> returnGoodsDTOs = order.getReturnGoods();
        if (returnGoodsDTOs == null || returnGoodsDTOs.isEmpty()) return;

        SaleOrderAction parent = new SaleOrderAction();
        parent.setId(Uid.random());
        parent.setUserCreated(request.getUserId());
        parent.setCentralBillSaleOrderId(saleOrderId);
        parent.setActionType(ActionType.RETURN);
        parent.setActionId(saleOrderId);
        saleOrderActionService.insertSelective(parent);

        SaleOrderActionLogging logging = new SaleOrderActionLogging();
        logging.setId(Uid.random());
        logging.setCentralBillSaleOrderActionId(parent.getId());
        logging.setType(LoggingType.CREATE);
        logging.setUserCreated(request.getUserId());
        BigDecimal total = returnGoodsDTOs.stream()
                .map(good -> good.getSkus().stream()
                        .map(ReturnGoodsSKUDTO::getQuantity)
                        .reduce(BigDecimal::add)
                        .orElse(BigDecimal.ZERO)
                )
                .reduce(BigDecimal::add)
                .orElse(BigDecimal.ZERO);
        logging.setTotal(total);
        BigDecimal amount = returnGoodsDTOs.stream()
                .map(good -> good.getSkus().stream()
                        .map(sku -> sku.getPrice().multiply(sku.getQuantity()))
                        .reduce(BigDecimal::add)
                        .orElse(BigDecimal.ZERO)
                )
                .reduce(BigDecimal::add)
                .orElse(BigDecimal.ZERO);
        logging.setAmount(amount);
        saleOrderActionLoggingService.insertSelective(logging);
        for (SaleOrderReturnGoodsDTO returnGoodsDTO : returnGoodsDTOs) {
            save(request, saleOrderId, parent, logging, returnGoodsDTO);
        }
    }

    private void save(SaleOrderCreateRequest request, String saleOrderId, SaleOrderAction parent, SaleOrderActionLogging logging, SaleOrderReturnGoodsDTO returnGoodsDTO) {
        ReturnGoods goods = new ReturnGoods();
        goods.setId(Uid.random());
        goods.setBossUnitId(request.getBossUnitId());
        goods.setUnitId(request.getUnitId());
        goods.setUserCreated(request.getUserId());
        goods.setGoodsId(returnGoodsDTO.getGoodsId());
        goods.setStyleId(returnGoodsDTO.getStyleId());
        goods.setPrice(returnGoodsDTO.getPrice());
        goods.setPriceChanged(returnGoodsDTO.isPriceChanged());
        returnGoodsService.insertSelective(goods);

        SaleOrderActionLoggingStyle loggingStyle = new SaleOrderActionLoggingStyle();
        loggingStyle.setId(Uid.random());
        loggingStyle.setCentralBillSaleOrderActionLoggingId(logging.getId());
        loggingStyle.setStyleId(goods.getStyleId());
        loggingStyle.setGoodsId(goods.getGoodsId());
        BigDecimal _total = returnGoodsDTO.getSkus().stream()
                .map(ReturnGoodsSKUDTO::getQuantity)
                .reduce(BigDecimal::add)
                .orElse(BigDecimal.ZERO);
        loggingStyle.setTotal(_total);
        BigDecimal _amount = returnGoodsDTO.getSkus().stream()
                .map(sku -> sku.getPrice().multiply(sku.getQuantity()))
                .reduce(BigDecimal::add)
                .orElse(BigDecimal.ZERO);
        loggingStyle.setAmount(_amount);
        loggingStyle.setUserCreated(request.getUserId());
        saleOrderActionLoggingStyleService.insertSelective(loggingStyle);

        SaleOrderAction action = new SaleOrderAction();
        action.setId(Uid.random());
        action.setParentId(parent.getId());
        action.setUserCreated(request.getUserId());
        action.setCentralBillSaleOrderId(saleOrderId);
        action.setActionType(ActionType.RETURN_GOODS);
        action.setActionId(goods.getId());
        saleOrderActionService.insertSelective(action);

        List<ReturnGoodsSKUDTO> skus = returnGoodsDTO.getSkus();
        saveReturnGoodsSkus(request, goods.getId(), loggingStyle, skus);

        List<RemarkDTO> remarks = returnGoodsDTO.getRemarks();
        saveReturnGoodsRemarks(request, goods.getId(), remarks);
    }

    private void saveReturnGoodsSkus(SaleOrderCreateRequest request, String returnGoodsId, SaleOrderActionLoggingStyle loggingStyle, List<ReturnGoodsSKUDTO> skus) {
        if (skus == null || skus.isEmpty()) return;

        for (ReturnGoodsSKUDTO dto : skus) {
            ReturnGoodsSKU sku = new ReturnGoodsSKU();
            sku.setId(Uid.random());
            sku.setCentralActionReturnGoodsId(returnGoodsId);
            sku.setSkuId(dto.getSkuId());
            sku.setQuantity(dto.getQuantity());
            sku.setPrice(dto.getPrice());
            sku.setUserCreated(request.getUserId());
            returnGoodsSKUService.insertSelective(sku);

            SaleOrderActionLoggingStyleSKU loggingStyleSKU = new SaleOrderActionLoggingStyleSKU();
            loggingStyleSKU.setId(Uid.random());
            loggingStyleSKU.setCentralBillSaleOrderActionLoggingStyleId(loggingStyle.getId());
            loggingStyleSKU.setSkuId(dto.getSkuId());
            loggingStyleSKU.setOldQuantity(BigDecimal.ZERO);
            loggingStyleSKU.setNewQuantity(dto.getQuantity());
            loggingStyleSKU.setOldPrice(BigDecimal.ZERO);
            loggingStyleSKU.setNewPrice(dto.getPrice());
            loggingStyleSKU.setUserCreated(request.getUserId());
            saleOrderActionLoggingStyleSKUService.insertSelective(loggingStyleSKU);
        }
    }

    private void saveReturnGoodsRemarks(SaleOrderCreateRequest request, String returnGoodsId, List<RemarkDTO> remarks) {
        if (remarks == null || remarks.isEmpty()) return;
        int sequence = 0;
        for (RemarkDTO dto : remarks) {
            ReturnGoodsRemark remark = new ReturnGoodsRemark();
            remark.setId(Uid.random());
            remark.setBossUnitId(request.getBossUnitId());
            remark.setUnitId(request.getUnitId());
            remark.setUserCreated(request.getUserId());
            remark.setCentralActionReturnGoodsId(returnGoodsId);
            remark.setRemark(dto.getRemark());
            remark.setSequence(sequence++);
            returnGoodsRemarkService.insertSelective(remark);
        }
    }

    /**
     * 重编辑销售单
     *
     * @param request 重编辑销售单请求参数
     * @return 销售单重编辑返回结果
     */
    @RequestMapping(value = "", method = RequestMethod.PUT)
    @ResponseBody
    public SaleOrderUpdateResponse update(@RequestBody SaleOrderUpdateRequest request) {
        String orderId = saleOrderService.doAtomically(() -> {
            // 保存销售单一级数据
            String saleOrderId = editSaleOrder(request);
            // 随动更新 二级数据 : 本单应付
            saleOrderService.updateAmountExpected(saleOrderId);
            //总收退款金额	销售单详情	随动更新	∑（所有收/退款记录金额）
            saleOrderService.updateAmountArrived(saleOrderId);
            //总核进/出金额	销售单详情	随动更新	∑（核进/出金额）
            saleOrderService.updateAmountCleared(saleOrderId);
            //结欠	销售单详情	随动更新	∑（sku开单数×sku开单单价 ± sku改欠货数×sku开单单价 -sku 退货数×sku退货单价） - 抹零金额 + ∑核进金额 - ∑核出金额 - ∑收款账户收退款金额
            saleOrderService.updateAmountRemain(saleOrderId);
            //销售总数	销售单详情	随动更新	∑（sku开单数） ± ∑（sku改欠货数）
            saleOrderService.updateTotalSold(saleOrderId);
            // 开单总数	销售单详情	随动更新	∑（sku开单数）
            saleOrderService.updateTotalCreated(saleOrderId);
            // 改欠货总数	销售单详情	随动更新	∑（sku改欠货数）
            saleOrderService.updateTotalModified(saleOrderId);
            //退货总数	销售单详情	随动更新	∑（sku退货数）
            saleOrderService.updateTotalReturned(saleOrderId);
            //发货数	发货/销售单详情	随动更新	∑（发货sku）
            saleOrderService.updateTotalDelivered(saleOrderId);
            // 欠货数	销售单详情	随动更新	∑（sku开单数） ± ∑（sku被改欠货数） - ∑（发货sku）
            saleOrderService.updateTotalOwed(saleOrderId);
            //总开单金额	销售单详情	随动更新	∑(sku开单数×sku金额)
            saleOrderService.updateAmountCreated(saleOrderId);
            // 总改欠货金额	销售单详情	随动更新	∑(sku改欠货数×sku金额)
            saleOrderService.updateAmountModified(saleOrderId);
            // 总退货金额	销售单详情	随动更新	∑(sku退货数×sku金额)
            saleOrderService.updateAmountReturned(saleOrderId);
            // 返回ID
            return saleOrderId;
        });
        SaleOrderUpdateResponse response = new SaleOrderUpdateResponse(0, "操作成功");
        response.setSaleOrderId(orderId);
        return response;
    }

    private String editSaleOrder(SaleOrderUpdateRequest request) {
        SaleOrderCreateDTO create = request.getSaleOrderCreate();
        String saleOrderId = create.getSaleOrderId();
        SaleOrder order = saleOrderService.selectById(saleOrderId);
        if (order == null) throw new IllegalArgumentException("销售单不存在");

        order.setCustomerId(create.getCustomerId());
        order.setAddressId(create.getAddressId());
        order.setDeliveryOnScene(create.isDeliveryOnScene());
        order.setOddment(create.getOddment());
        order.setUserUpdated(request.getUserId());
        order.setLastUpdated(new Date());
        saleOrderService.updateById(order);

        editRemittance(request);
        editCreateGoods(request);
        editModifyGoods(request);
        editReturnGoods(request);
        editRemarks(request);

        return request.getSaleOrderCreate().getSaleOrderId();
    }

    private void editRemarks(SaleOrderUpdateRequest request) {
        SaleOrderCreateDTO order = request.getSaleOrderCreate();
        String saleOrderId = order.getSaleOrderId();

        List<SaleOrderRemark> remarks = saleOrderRemarkService.getSaleOrderRemarks(saleOrderId);
        Map<String, SaleOrderRemark> map = remarks.stream()
                .peek(remark -> {
                    remark.setTimeDeleted(System.currentTimeMillis());
                    remark.setUserDeleted(request.getUserId());
                })
                .collect(Collectors.toMap(SaleOrderRemark::getId, remark -> remark));

        List<SaleOrderRemarkDTO> dtos = order.getRemarks() != null ? order.getRemarks() : Collections.emptyList();
        Date now = new Date();
        AtomicInteger sequence = new AtomicInteger(0);
        // 新增
        dtos.stream()
                .filter(dto -> dto.getRemarkId() == null)
                .forEach(dto -> {
                    SaleOrderRemark remark = new SaleOrderRemark();
                    remark.setId(Uid.random());
                    remark.setRemark(dto.getRemark());
                    remark.setDateRemarked(StringUtils.isEmpty(dto.getDateRemarked()) ? now : TimeKit.parse(dto.getDateRemarked(), "yyyy-MM-dd HH:mm:ss"));
                    remark.setCentralBillSaleOrderId(saleOrderId);
                    remark.setSequence(sequence.getAndIncrement());
                    remark.setUserCreated(request.getUserId());
                    saleOrderRemarkService.insertSelective(remark);
                });

        // 编辑
        dtos.stream()
                .filter(dto -> dto.getRemarkId() != null)
                .forEach(dto -> {
                    SaleOrderRemark remark = map.get(dto.getRemarkId());
                    if (remark == null) throw new IllegalArgumentException("销售单备注记录不存在: " + dto.getRemarkId());
                    remark.setTimeDeleted(0L);
                    remark.setUserDeleted(null);
                    remark.setRemark(dto.getRemark());
                    remark.setDateRemarked(StringUtils.isEmpty(dto.getDateRemarked()) ? now : TimeKit.parse(dto.getDateRemarked(), "yyyy-MM-dd HH:mm:ss"));
                    saleOrderRemarkService.updateByPrimaryKeySelective(remark);
                });

        // 还是删除状态的就伪删除掉
        remarks.stream()
                .filter(remark -> remark.getTimeDeleted() > 0L)
                .forEach(remark -> saleOrderRemarkService.updateByPrimaryKeySelective(remark));
    }

    private void editRemittance(SaleOrderUpdateRequest request) {
        SaleOrderCreateDTO order = request.getSaleOrderCreate();
        String saleOrderId = order.getSaleOrderId();
        Date now = new Date();
        List<Remittance> remittances = remittanceService.getSaleOrderRemittances(saleOrderId);
        Map<String, Remittance> map = remittances.stream()
                .peek(remittance -> {
                    remittance.setCanceled(true);
                    remittance.setUserCanceled(request.getUserId());
                    remittance.setDateCanceled(now);
                })
                .collect(Collectors.toMap(Remittance::getId, remittance -> remittance));

        // 新增
        List<RemittanceDTO> ins = order.getInRemittances() != null ? order.getInRemittances() : Collections.emptyList();
        ins.stream()
                .filter(dto -> dto.getRemittanceId() == null)
                .peek(dto -> dto.setRemittanceType(RemittanceType.RECEIPT))
                .forEach(dto -> save(request, saleOrderId, now, order, dto));

        List<RemittanceDTO> outs = order.getOutRemittances() != null ? order.getOutRemittances() : Collections.emptyList();
        outs.stream()
                .filter(dto -> dto.getRemittanceId() == null)
                .peek(dto -> dto.setRemittanceType(RemittanceType.REFUND))
                .forEach(dto -> save(request, saleOrderId, now, order, dto));

        // 修改
        ins.stream()
                .filter(dto -> dto.getRemittanceId() != null)
                .peek(dto -> dto.setRemittanceType(RemittanceType.RECEIPT))
                .forEach(dto -> {
                    Remittance remittance = map.get(dto.getRemittanceId());
                    if (remittance == null) throw new IllegalArgumentException("收款记录不存在: " + dto.getRemittanceId());

                    remittance.setCanceled(false);
                    remittance.setUserCanceled(null);
                    remittance.setDateCanceled(null);
                    remittance.setType(dto.getRemittanceType());
                    remittance.setRemitMethodId(dto.getRemitMethodId());
                    remittance.setAmount(dto.getAmount());

                    // 变动记录
                    RemittanceLogging logging = new RemittanceLogging();
                    logging.setId(Uid.random());
                    logging.setCentralActionRemittanceId(remittance.getId());
                    logging.setType(LoggingType.MODIFY);
                    logging.setOldAmount(remittance.getAmount());
                    logging.setNewAmount(dto.getAmount());
                    logging.setOldRemitMethodId(remittance.getRemitMethodId());
                    logging.setNewRemitMethodId(dto.getRemitMethodId());
                    logging.setOldDateOperated(remittance.getDateOperated());
                    logging.setNewDateOperated(now);
                    logging.setUserCreated(request.getUserId());
                    if (logging.modified()) remittanceLoggingService.insertSelective(logging);
                });

        outs.stream()
                .filter(dto -> dto.getRemittanceId() != null)
                .peek(dto -> dto.setRemittanceType(RemittanceType.REFUND))
                .forEach(dto -> {
                    Remittance remittance = map.get(dto.getRemittanceId());
                    if (remittance == null) throw new IllegalArgumentException("收款记录不存在: " + dto.getRemittanceId());

                    remittance.setCanceled(false);
                    remittance.setUserCanceled(null);
                    remittance.setDateCanceled(null);
                    remittance.setType(dto.getRemittanceType());
                    remittance.setRemitMethodId(dto.getRemitMethodId());
                    remittance.setAmount(dto.getAmount());

                    // 变动记录
                    RemittanceLogging logging = new RemittanceLogging();
                    logging.setId(Uid.random());
                    logging.setCentralActionRemittanceId(remittance.getId());
                    logging.setType(LoggingType.MODIFY);
                    logging.setOldAmount(remittance.getAmount());
                    logging.setNewAmount(dto.getAmount());
                    logging.setOldRemitMethodId(remittance.getRemitMethodId());
                    logging.setNewRemitMethodId(dto.getRemitMethodId());
                    logging.setOldDateOperated(remittance.getDateOperated());
                    logging.setNewDateOperated(now);
                    logging.setUserCreated(request.getUserId());
                    if (logging.modified()) remittanceLoggingService.insertSelective(logging);
                });
    }

    private void editCreateGoods(SaleOrderUpdateRequest request) {
        SaleOrderCreateDTO create = request.getSaleOrderCreate();
        String thisOrderId = create.getSaleOrderId();
        List<SaleOrderCreateGoodsDTO> dtos = create.getCreateGoods() != null ? create.getCreateGoods() : Collections.emptyList();

        Date now = new Date();

        List<CreateGoods> goods = createGoodsService.getSaleOrderCreateGoods(thisOrderId);
        Map<String, CreateGoods> map = goods.stream().peek(good -> {
            good.setCanceled(true);
            good.setUserCanceled(request.getUserId());
            good.setDateCanceled(now);
        }).collect(Collectors.toMap(CreateGoods::getId, good -> good));

        SaleOrderAction parent = saleOrderActionService.selectByOrderIdAndActionType(thisOrderId, ActionType.CREATE);
        if (parent == null) {
            parent = new SaleOrderAction();
            parent.setId(Uid.random());
            parent.setUserCreated(request.getUserId());
            parent.setCentralBillSaleOrderId(thisOrderId);
            parent.setActionType(ActionType.CREATE);
            parent.setActionId(thisOrderId);
            saleOrderActionService.insertSelective(parent);
        }
        SaleOrderActionLogging logging = new SaleOrderActionLogging();
        logging.setId(Uid.random());
        logging.setType(LoggingType.MODIFY);
        logging.setUserCreated(request.getUserId());
        logging.setCentralBillSaleOrderActionId(parent.getId());
        saleOrderActionLoggingService.insertSelective(logging);
        // 新增
        final SaleOrderAction finalParent = parent;
        dtos.stream()
                .filter(dto -> dto.getCreateGoodsId() == null)
                .forEach(dto -> save(request, thisOrderId, finalParent, logging, dto));

        // 编辑
        dtos.stream()
                .filter(dto -> dto.getCreateGoodsId() != null)
                .forEach((SaleOrderCreateGoodsDTO dto) -> {
                    String createGoodsId = dto.getCreateGoodsId();
                    CreateGoods old = map.get(createGoodsId);
                    if (old == null) throw new IllegalArgumentException("货品不存在:" + createGoodsId);

                    old.setCouponType(dto.getCouponType());
                    old.setPrice(dto.getPrice());
                    old.setPriceChanged(dto.isPriceChanged());
                    old.setDiscount(dto.getDiscount());
                    old.setCouponTemplateId(dto.getCouponTemplateId());
                    old.setLastUpdated(now);
                    old.setUserUpdated(request.getUserId());
                    old.setCanceled(false);
                    old.setUserCanceled(null);
                    old.setDateCanceled(null);
                    createGoodsService.updateByPrimaryKeySelective(old);

                    editCreateGoodsRemarks(request, dto);

                    // 变动日志
                    List<SaleOrderActionLoggingStyleSKU> loggingStyleSKUS = new ArrayList<>();
                    Map<String, CreateGoodsSKU> m = old.getSkus().stream().collect(Collectors.toMap(CreateGoodsSKU::getId, sku -> sku));
                    dto.getSkus().forEach(sku -> {
                        String createGoodsSKUId = sku.getCreateGoodsSKUId();
                        CreateGoodsSKU oldSKU = m.get(createGoodsSKUId);
                        if (oldSKU == null) throw new IllegalArgumentException("货品SKU不存在:" + createGoodsSKUId);

                        SaleOrderActionLoggingStyleSKU loggingStyleSKU = new SaleOrderActionLoggingStyleSKU();
                        loggingStyleSKU.setId(Uid.random());
                        loggingStyleSKU.setOldPrice(oldSKU.getPrice());
                        loggingStyleSKU.setNewPrice(sku.getPrice());
                        loggingStyleSKU.setOldQuantity(oldSKU.getQuantity());
                        loggingStyleSKU.setNewQuantity(sku.getQuantity());
                        loggingStyleSKU.setUserCreated(request.getUserId());
                        loggingStyleSKU.setSkuId(oldSKU.getSkuId());
                        loggingStyleSKUS.add(loggingStyleSKU);

                        oldSKU.setQuantity(sku.getQuantity());
                        oldSKU.setPrice(sku.getPrice());
                        createGoodsSKUService.updateByPrimaryKeySelective(oldSKU);
                    });

                    boolean modified = loggingStyleSKUS.stream().map(SaleOrderActionLoggingStyleSKU::modified).reduce((a, b) -> a || b).orElse(false);
                    // 没有任何修改
                    if (!modified) return;

                    SaleOrderActionLoggingStyle loggingStyle = new SaleOrderActionLoggingStyle();
                    loggingStyle.setId(Uid.random());
                    loggingStyle.setCentralBillSaleOrderActionLoggingId(logging.getId());
                    loggingStyle.setStyleId(dto.getStyleId());
                    loggingStyle.setGoodsId(dto.getGoodsId());
                    BigDecimal totalInc = loggingStyleSKUS.stream()
                            .filter(sku -> sku.total().compareTo(BigDecimal.ZERO) > 0)
                            .map(SaleOrderActionLoggingStyleSKU::total)
                            .reduce(BigDecimal::add)
                            .orElse(BigDecimal.ZERO);
                    loggingStyle.setTotalInc(totalInc);
                    BigDecimal totalDec = loggingStyleSKUS.stream()
                            .filter(sku -> sku.total().compareTo(BigDecimal.ZERO) < 0)
                            .map(SaleOrderActionLoggingStyleSKU::total)
                            .reduce(BigDecimal::add)
                            .orElse(BigDecimal.ZERO);
                    loggingStyle.setTotalDec(totalDec);
                    loggingStyle.setTotal(totalInc.add(totalDec));

                    BigDecimal amountInc = loggingStyleSKUS.stream()
                            .filter(sku -> sku.amount().compareTo(BigDecimal.ZERO) > 0)
                            .map(SaleOrderActionLoggingStyleSKU::amount)
                            .reduce(BigDecimal::add)
                            .orElse(BigDecimal.ZERO);
                    loggingStyle.setAmountInc(amountInc);
                    BigDecimal amountDec = loggingStyleSKUS.stream()
                            .filter(sku -> sku.amount().compareTo(BigDecimal.ZERO) < 0)
                            .map(SaleOrderActionLoggingStyleSKU::amount)
                            .reduce(BigDecimal::add)
                            .orElse(BigDecimal.ZERO);
                    loggingStyle.setAmountDec(amountDec);
                    loggingStyle.setAmount(amountInc.add(amountDec));
                    loggingStyle.setUserCreated(request.getUserId());
                    saleOrderActionLoggingStyleService.insertSelective(loggingStyle);

                    loggingStyleSKUS.forEach(sku -> {
                        sku.setCentralBillSaleOrderActionLoggingStyleId(loggingStyle.getId());
                        saleOrderActionLoggingStyleSKUService.insertSelective(sku);
                    });
                });

        // 还是撤销状态的就是要撤销的货品
        List<String> actionIds = goods.stream()
                .filter(good -> Boolean.TRUE.equals(good.getCanceled()))
                .map(good -> {
                    createGoodsService.updateByPrimaryKeySelective(good);
                    return good.getId();
                })
                .collect(Collectors.toList());
        saleOrderActionService.cancel(ActionType.CREATE_GOODS, request.getUserId(), actionIds.toArray(new String[0]));
    }

    private void editCreateGoodsRemarks(SaleOrderUpdateRequest request, SaleOrderCreateGoodsDTO createGoodsDTO) {
        List<CreateGoodsRemark> remarks = createGoodsRemarkService.getCreateGoodsRemarks(createGoodsDTO.getCreateGoodsId());
        Map<String, CreateGoodsRemark> map = remarks.stream()
                .peek(remark -> {
                    remark.setTimeDeleted(System.currentTimeMillis());
                    remark.setUserDeleted(request.getUserId());
                })
                .collect(Collectors.toMap(CreateGoodsRemark::getId, remark -> remark));
        List<RemarkDTO> dtos = createGoodsDTO.getRemarks() != null ? createGoodsDTO.getRemarks() : Collections.emptyList();
        // 新增
        List<RemarkDTO> saves = dtos.stream()
                .filter(dto -> dto.getRemarkId() == null)
                .collect(Collectors.toList());
        saveCreateGoodsRemarks(request, createGoodsDTO.getCreateGoodsId(), saves);

        // 编辑
        dtos.stream()
                .filter(dto -> dto.getRemarkId() != null)
                .forEach(dto -> {
                    CreateGoodsRemark remark = map.get(dto.getRemarkId());
                    if (remark == null) throw new IllegalArgumentException("开单货品备注不存在: " + dto.getRemarkId());
                    remark.setTimeDeleted(0L);
                    remark.setUserDeleted(null);
                    remark.setRemark(dto.getRemark());
                    remark.setUserUpdated(request.getUserId());
                    createGoodsRemarkService.updateByPrimaryKeySelective(remark);
                });

        // 更新依然是删除状态的备注
        remarks.stream()
                .filter(remark -> remark.getTimeDeleted() > 0L)
                .forEach(remark -> createGoodsRemarkService.updateByPrimaryKeySelective(remark));
    }

    private void editModifyGoods(SaleOrderUpdateRequest request) {
        SaleOrderCreateDTO create = request.getSaleOrderCreate();
        String thisOrderId = create.getSaleOrderId();
        List<SaleOrderModifyGoodsDTO> dtos = create.getModifyGoods();

        Date now = new Date();
        List<ModifyGoods> goods = modifyGoodsService.getSaleOrderModifyGoods(thisOrderId, ActionType.MODIFY_THAT_GOODS);
        Map<String, ModifyGoods> map = goods.stream().peek(good -> {
            good.setCanceled(true);
            good.setUserCanceled(request.getUserId());
            good.setDateCanceled(now);
        }).collect(Collectors.toMap(ModifyGoods::getId, good -> good));

        SaleOrderAction parent = saleOrderActionService.selectByOrderIdAndActionType(thisOrderId, ActionType.MODIFY_THAT);
        if (parent == null) {
            parent = new SaleOrderAction();
            parent.setId(Uid.random());
            parent.setUserCreated(request.getUserId());
            parent.setCentralBillSaleOrderId(thisOrderId);
            parent.setActionType(ActionType.MODIFY_THAT);
            parent.setActionId(thisOrderId);
            saleOrderActionService.insertSelective(parent);
        }

        SaleOrderActionLogging logging = new SaleOrderActionLogging();
        logging.setId(Uid.random());
        logging.setType(LoggingType.MODIFY);
        logging.setUserCreated(request.getUserId());
        logging.setCentralBillSaleOrderActionId(parent.getId());
        saleOrderActionLoggingService.insertSelective(logging);

        // 新增
        Map<String, SaleOrderAction> modifyThatParents = new HashMap<>();
        Map<String, SaleOrderActionLogging> modifyThatLoggings = new HashMap<>();
        Map<String, SaleOrderAction> modifyThisParents = new HashMap<>();
        Map<String, SaleOrderActionLogging> modifyThisLoggings = new HashMap<>();
        dtos.stream()
                .filter(dto -> dto.getModifyGoodsId() == null)
                .forEach(dto -> save(request, thisOrderId, dtos, modifyThatParents, modifyThatLoggings, modifyThisParents, modifyThisLoggings, dto));

        // 编辑
        dtos.stream()
                .filter(dto -> dto.getModifyGoodsId() != null)
                .forEach(dto -> {
                    String createGoodsId = dto.getModifyGoodsId();
                    ModifyGoods old = map.get(createGoodsId);
                    if (old == null) throw new IllegalArgumentException("货品不存在:" + createGoodsId);

                    old.setLastUpdated(now);
                    old.setUserUpdated(request.getUserId());
                    old.setCanceled(false);
                    old.setUserCanceled(null);
                    old.setDateCanceled(null);
                    modifyGoodsService.updateByPrimaryKeySelective(old);

                    // 变动日志
                    List<SaleOrderActionLoggingStyleSKU> thatLoggingStyleSKUS = new ArrayList<>();
                    List<SaleOrderActionLoggingStyleSKU> thisLoggingStyleSKUS = new ArrayList<>();
                    Map<String, ModifyGoodsSKU> m = old.getSkus().stream().collect(Collectors.toMap(ModifyGoodsSKU::getId, sku -> sku));
                    dto.getSkus().forEach(sku -> {
                        String modifyGoodsSKUId = sku.getModifyGoodsSKUId();
                        ModifyGoodsSKU oldSKU = m.get(modifyGoodsSKUId);
                        if (oldSKU == null) throw new IllegalArgumentException("货品SKU不存在:" + modifyGoodsSKUId);

                        {
                            SaleOrderActionLoggingStyleSKU thatLoggingStyleSKU = new SaleOrderActionLoggingStyleSKU();
                            thatLoggingStyleSKU.setId(Uid.random());
                            thatLoggingStyleSKU.setOldPrice(oldSKU.getPrice());
                            thatLoggingStyleSKU.setNewPrice(sku.getPrice());
                            thatLoggingStyleSKU.setOldQuantity(oldSKU.getQuantity());
                            thatLoggingStyleSKU.setNewQuantity(sku.getQuantity());
                            thatLoggingStyleSKU.setUserCreated(request.getUserId());
                            thatLoggingStyleSKU.setSkuId(oldSKU.getSkuId());
                            thatLoggingStyleSKUS.add(thatLoggingStyleSKU);
                        }
                        {
                            SaleOrderActionLoggingStyleSKU thisLoggingStyleSKU = new SaleOrderActionLoggingStyleSKU();
                            thisLoggingStyleSKU.setId(Uid.random());
                            thisLoggingStyleSKU.setOldPrice(oldSKU.getPrice());
                            thisLoggingStyleSKU.setNewPrice(sku.getPrice());
                            thisLoggingStyleSKU.setOldQuantity(oldSKU.getQuantity());
                            thisLoggingStyleSKU.setNewQuantity(sku.getQuantity());
                            thisLoggingStyleSKU.setUserCreated(request.getUserId());
                            thisLoggingStyleSKU.setSkuId(oldSKU.getSkuId());
                            thisLoggingStyleSKUS.add(thisLoggingStyleSKU);
                        }

                        oldSKU.setQuantity(sku.getQuantity());
                        oldSKU.setPrice(sku.getPrice());
                        modifyGoodsSKUService.updateByPrimaryKeySelective(oldSKU);
                    });

                    {
                        boolean modified = thatLoggingStyleSKUS.stream().map(SaleOrderActionLoggingStyleSKU::modified).reduce((a, b) -> a || b).orElse(false);
                        // 没有任何修改
                        if (!modified) return;

                        SaleOrderActionLoggingStyle thatLoggingStyle = new SaleOrderActionLoggingStyle();
                        thatLoggingStyle.setId(Uid.random());
                        thatLoggingStyle.setCentralBillSaleOrderActionLoggingId(logging.getId());
                        thatLoggingStyle.setStyleId(dto.getStyleId());
                        thatLoggingStyle.setGoodsId(dto.getGoodsId());
                        BigDecimal totalInc = thatLoggingStyleSKUS.stream()
                                .filter(sku -> sku.total().compareTo(BigDecimal.ZERO) > 0)
                                .map(SaleOrderActionLoggingStyleSKU::total)
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO);
                        thatLoggingStyle.setTotalInc(totalInc);
                        BigDecimal totalDec = thatLoggingStyleSKUS.stream()
                                .filter(sku -> sku.total().compareTo(BigDecimal.ZERO) < 0)
                                .map(SaleOrderActionLoggingStyleSKU::total)
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO);
                        thatLoggingStyle.setTotalDec(totalDec);
                        thatLoggingStyle.setTotal(totalInc.add(totalDec));

                        BigDecimal amountInc = thatLoggingStyleSKUS.stream()
                                .filter(sku -> sku.amount().compareTo(BigDecimal.ZERO) > 0)
                                .map(SaleOrderActionLoggingStyleSKU::amount)
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO);
                        thatLoggingStyle.setAmountInc(amountInc);
                        BigDecimal amountDec = thatLoggingStyleSKUS.stream()
                                .filter(sku -> sku.amount().compareTo(BigDecimal.ZERO) < 0)
                                .map(SaleOrderActionLoggingStyleSKU::amount)
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO);
                        thatLoggingStyle.setAmountDec(amountDec);
                        thatLoggingStyle.setAmount(amountInc.add(amountDec));
                        thatLoggingStyle.setUserCreated(request.getUserId());
                        saleOrderActionLoggingStyleService.insertSelective(thatLoggingStyle);

                        thatLoggingStyleSKUS.forEach(sku -> {
                            sku.setCentralBillSaleOrderActionLoggingStyleId(thatLoggingStyle.getId());
                            saleOrderActionLoggingStyleSKUService.insertSelective(sku);
                        });
                    }

                    {
                        boolean modified = thisLoggingStyleSKUS.stream().map(SaleOrderActionLoggingStyleSKU::modified).reduce((a, b) -> a || b).orElse(false);
                        // 没有任何修改
                        if (!modified) return;

                        SaleOrderActionLoggingStyle thisLoggingStyle = new SaleOrderActionLoggingStyle();
                        thisLoggingStyle.setId(Uid.random());
                        thisLoggingStyle.setCentralBillSaleOrderActionLoggingId(logging.getId());
                        thisLoggingStyle.setStyleId(dto.getStyleId());
                        thisLoggingStyle.setGoodsId(dto.getGoodsId());
                        BigDecimal totalInc = thisLoggingStyleSKUS.stream()
                                .filter(sku -> sku.total().compareTo(BigDecimal.ZERO) > 0)
                                .map(SaleOrderActionLoggingStyleSKU::total)
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO);
                        thisLoggingStyle.setTotalInc(totalInc);
                        BigDecimal totalDec = thisLoggingStyleSKUS.stream()
                                .filter(sku -> sku.total().compareTo(BigDecimal.ZERO) < 0)
                                .map(SaleOrderActionLoggingStyleSKU::total)
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO);
                        thisLoggingStyle.setTotalDec(totalDec);
                        thisLoggingStyle.setTotal(totalInc.add(totalDec));

                        BigDecimal amountInc = thisLoggingStyleSKUS.stream()
                                .filter(sku -> sku.amount().compareTo(BigDecimal.ZERO) > 0)
                                .map(SaleOrderActionLoggingStyleSKU::amount)
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO);
                        thisLoggingStyle.setAmountInc(amountInc);
                        BigDecimal amountDec = thisLoggingStyleSKUS.stream()
                                .filter(sku -> sku.amount().compareTo(BigDecimal.ZERO) < 0)
                                .map(SaleOrderActionLoggingStyleSKU::amount)
                                .reduce(BigDecimal::add)
                                .orElse(BigDecimal.ZERO);
                        thisLoggingStyle.setAmountDec(amountDec);
                        thisLoggingStyle.setAmount(amountInc.add(amountDec));
                        thisLoggingStyle.setUserCreated(request.getUserId());
                        saleOrderActionLoggingStyleService.insertSelective(thisLoggingStyle);

                        thisLoggingStyleSKUS.forEach(sku -> {
                            sku.setCentralBillSaleOrderActionLoggingStyleId(thisLoggingStyle.getId());
                            saleOrderActionLoggingStyleSKUService.insertSelective(sku);
                        });
                    }

                });

        // 还是撤销状态的就是要撤销的货品
        List<String> actionIds = goods.stream()
                .filter(good -> Boolean.TRUE.equals(good.getCanceled()))
                .map(good -> {
                    modifyGoodsService.updateByPrimaryKeySelective(good);
                    return good.getId();
                })
                .collect(Collectors.toList());
        saleOrderActionService.cancel(ActionType.MODIFY_THAT_GOODS, request.getUserId(), actionIds.toArray(new String[0]));
        saleOrderActionService.cancel(ActionType.MODIFY_THIS_GOODS, request.getUserId(), actionIds.toArray(new String[0]));
    }

    private void editReturnGoods(SaleOrderUpdateRequest request) {
        SaleOrderCreateDTO create = request.getSaleOrderCreate();
        String thisOrderId = create.getSaleOrderId();
        List<SaleOrderReturnGoodsDTO> dtos = create.getReturnGoods() != null ? create.getReturnGoods() : Collections.emptyList();

        Date now = new Date();

        List<ReturnGoods> goods = returnGoodsService.getSaleOrderReturnGoods(thisOrderId);
        Map<String, ReturnGoods> map = goods.stream().peek(good -> {
            good.setCanceled(true);
            good.setUserCanceled(request.getUserId());
            good.setDateCanceled(now);
        }).collect(Collectors.toMap(ReturnGoods::getId, good -> good));

        SaleOrderAction parent = saleOrderActionService.selectByOrderIdAndActionType(thisOrderId, ActionType.RETURN);
        if (parent == null) {
            parent = new SaleOrderAction();
            parent.setId(Uid.random());
            parent.setUserCreated(request.getUserId());
            parent.setCentralBillSaleOrderId(thisOrderId);
            parent.setActionType(ActionType.RETURN);
            parent.setActionId(thisOrderId);
            saleOrderActionService.insertSelective(parent);
        }
        SaleOrderActionLogging logging = new SaleOrderActionLogging();
        logging.setId(Uid.random());
        logging.setType(LoggingType.MODIFY);
        logging.setUserCreated(request.getUserId());
        logging.setCentralBillSaleOrderActionId(parent.getId());
        saleOrderActionLoggingService.insertSelective(logging);
        // 新增
        final SaleOrderAction finalParent = parent;
        dtos.stream()
                .filter(dto -> dto.getReturnGoodsId() == null)
                .forEach(dto -> save(request, thisOrderId, finalParent, logging, dto));

        // 编辑
        dtos.stream()
                .filter(dto -> dto.getReturnGoodsId() != null)
                .forEach((SaleOrderReturnGoodsDTO dto) -> {
                    String returnGoodsId = dto.getReturnGoodsId();
                    ReturnGoods old = map.get(returnGoodsId);
                    if (old == null) throw new IllegalArgumentException("货品不存在:" + returnGoodsId);

                    old.setPrice(dto.getPrice());
                    old.setPriceChanged(dto.isPriceChanged());
                    old.setLastUpdated(now);
                    old.setUserUpdated(request.getUserId());
                    old.setCanceled(false);
                    old.setUserCanceled(null);
                    old.setDateCanceled(null);
                    returnGoodsService.updateByPrimaryKeySelective(old);

                    editReturnGoodsRemarks(request, dto);

                    // 变动日志
                    List<SaleOrderActionLoggingStyleSKU> loggingStyleSKUS = new ArrayList<>();
                    Map<String, ReturnGoodsSKU> m = old.getSkus().stream().collect(Collectors.toMap(ReturnGoodsSKU::getId, sku -> sku));
                    dto.getSkus().forEach(sku -> {
                        String returnGoodsSKUId = sku.getReturnGoodsSKUId();
                        ReturnGoodsSKU oldSKU = m.get(returnGoodsSKUId);
                        if (oldSKU == null) throw new IllegalArgumentException("货品SKU不存在:" + returnGoodsSKUId);

                        SaleOrderActionLoggingStyleSKU loggingStyleSKU = new SaleOrderActionLoggingStyleSKU();
                        loggingStyleSKU.setId(Uid.random());
                        loggingStyleSKU.setOldPrice(oldSKU.getPrice());
                        loggingStyleSKU.setNewPrice(sku.getPrice());
                        loggingStyleSKU.setOldQuantity(oldSKU.getQuantity());
                        loggingStyleSKU.setNewQuantity(sku.getQuantity());
                        loggingStyleSKU.setUserCreated(request.getUserId());
                        loggingStyleSKU.setSkuId(oldSKU.getSkuId());
                        loggingStyleSKUS.add(loggingStyleSKU);

                        oldSKU.setQuantity(sku.getQuantity());
                        oldSKU.setPrice(sku.getPrice());
                        returnGoodsSKUService.updateByPrimaryKeySelective(oldSKU);
                    });

                    boolean modified = loggingStyleSKUS.stream().map(SaleOrderActionLoggingStyleSKU::modified).reduce((a, b) -> a || b).orElse(false);
                    // 没有任何修改
                    if (!modified) return;

                    SaleOrderActionLoggingStyle loggingStyle = new SaleOrderActionLoggingStyle();
                    loggingStyle.setId(Uid.random());
                    loggingStyle.setCentralBillSaleOrderActionLoggingId(logging.getId());
                    loggingStyle.setStyleId(dto.getStyleId());
                    loggingStyle.setGoodsId(dto.getGoodsId());
                    BigDecimal totalInc = loggingStyleSKUS.stream()
                            .filter(sku -> sku.total().compareTo(BigDecimal.ZERO) > 0)
                            .map(SaleOrderActionLoggingStyleSKU::total)
                            .reduce(BigDecimal::add)
                            .orElse(BigDecimal.ZERO);
                    loggingStyle.setTotalInc(totalInc);
                    BigDecimal totalDec = loggingStyleSKUS.stream()
                            .filter(sku -> sku.total().compareTo(BigDecimal.ZERO) < 0)
                            .map(SaleOrderActionLoggingStyleSKU::total)
                            .reduce(BigDecimal::add)
                            .orElse(BigDecimal.ZERO);
                    loggingStyle.setTotalDec(totalDec);
                    loggingStyle.setTotal(totalInc.add(totalDec));

                    BigDecimal amountInc = loggingStyleSKUS.stream()
                            .filter(sku -> sku.amount().compareTo(BigDecimal.ZERO) > 0)
                            .map(SaleOrderActionLoggingStyleSKU::amount)
                            .reduce(BigDecimal::add)
                            .orElse(BigDecimal.ZERO);
                    loggingStyle.setAmountInc(amountInc);
                    BigDecimal amountDec = loggingStyleSKUS.stream()
                            .filter(sku -> sku.amount().compareTo(BigDecimal.ZERO) < 0)
                            .map(SaleOrderActionLoggingStyleSKU::amount)
                            .reduce(BigDecimal::add)
                            .orElse(BigDecimal.ZERO);
                    loggingStyle.setAmountDec(amountDec);
                    loggingStyle.setAmount(amountInc.add(amountDec));
                    loggingStyle.setUserCreated(request.getUserId());
                    saleOrderActionLoggingStyleService.insertSelective(loggingStyle);

                    loggingStyleSKUS.forEach(sku -> {
                        sku.setCentralBillSaleOrderActionLoggingStyleId(loggingStyle.getId());
                        saleOrderActionLoggingStyleSKUService.insertSelective(sku);
                    });
                });

        // 还是撤销状态的就是要撤销的货品
        List<String> actionIds = goods.stream()
                .filter(good -> Boolean.TRUE.equals(good.getCanceled()))
                .map(good -> {
                    returnGoodsService.updateByPrimaryKeySelective(good);
                    return good.getId();
                })
                .collect(Collectors.toList());
        saleOrderActionService.cancel(ActionType.RETURN_GOODS, request.getUserId(), actionIds.toArray(new String[0]));
    }

    private void editReturnGoodsRemarks(SaleOrderUpdateRequest request, SaleOrderReturnGoodsDTO returnGoodsDTO) {
        List<ReturnGoodsRemark> remarks = returnGoodsRemarkService.getReturnGoodsRemarks(returnGoodsDTO.getReturnGoodsId());
        Map<String, ReturnGoodsRemark> map = remarks.stream()
                .peek(remark -> {
                    remark.setTimeDeleted(System.currentTimeMillis());
                    remark.setUserDeleted(request.getUserId());
                })
                .collect(Collectors.toMap(ReturnGoodsRemark::getId, remark -> remark));
        List<RemarkDTO> dtos = returnGoodsDTO.getRemarks() != null ? returnGoodsDTO.getRemarks() : Collections.emptyList();
        // 新增
        List<RemarkDTO> saves = dtos.stream()
                .filter(dto -> dto.getRemarkId() == null)
                .collect(Collectors.toList());
        saveReturnGoodsRemarks(request, returnGoodsDTO.getReturnGoodsId(), saves);

        // 编辑
        dtos.stream()
                .filter(dto -> dto.getRemarkId() != null)
                .forEach(dto -> {
                    ReturnGoodsRemark remark = map.get(dto.getRemarkId());
                    if (remark == null) throw new IllegalArgumentException("退货货品备注不存在: " + dto.getRemarkId());
                    remark.setTimeDeleted(0L);
                    remark.setUserDeleted(null);
                    remark.setRemark(dto.getRemark());
                    remark.setUserUpdated(request.getUserId());
                    returnGoodsRemarkService.updateByPrimaryKeySelective(remark);
                });

        // 更新依然是删除状态的备注
        remarks.stream()
                .filter(remark -> remark.getTimeDeleted() > 0L)
                .forEach(remark -> returnGoodsRemarkService.updateByPrimaryKeySelective(remark));
    }

    /**
     * 更新跟单人
     *
     * @param request 更新跟单人请求参数
     * @return 更新跟单人请求结果
     */
    @RequestMapping(value = "/{orderId}/merchandiser", method = RequestMethod.PUT)
    @ResponseBody
    public SaleOrderMerchandiserUpdateResponse update(
            @PathVariable("orderId") String orderId,
            @RequestBody SaleOrderMerchandiserUpdateRequest request
    ) {
        saleOrderService.setMerchandiser(orderId, request.getMerchandiserId());
        return new SaleOrderMerchandiserUpdateResponse();
    }

    /**
     * 更新销售单收货地址
     *
     * @param request 更新销售单收货地址请求参数
     * @return 更新销售单收货地址请求结果
     */
    @RequestMapping(value = "/address", method = RequestMethod.PUT)
    @ResponseBody
    public SaleOrderAddressUpdateResponse updateAddress(
            @RequestBody SaleOrderAddressUpdateRequest request
    ) {
        saleOrderService.setAddress(request.getSaleOrderId(), request.getAddressId());
        return new SaleOrderAddressUpdateResponse();
    }

    /**
     * 获取上次拿货价
     *
     * @param goodsId    货品ID
     * @param customerId 客户ID
     * @param unitId     UNIT_ID
     * @return 该货品在该组织的该货品上次拿货价
     */
    @RequestMapping(value = "/goods/{goodsId}/customer/{customerId}/price", method = RequestMethod.GET)
    @ResponseBody
    public SaleOrderLastPriceResponse getLastPrice(
            @PathVariable("goodsId") String goodsId,
            @PathVariable("customerId") String customerId,
            @RequestParam("unitId") String unitId
    ) {
        BigDecimal price = saleOrderService.getLastPrice(unitId, goodsId, customerId);
        SaleOrderLastPriceResponse response = new SaleOrderLastPriceResponse();
        response.setPrice(price);
        return response;
    }

    /**
     * 获取客户的历史订单
     *
     * @param unitId          组织ID
     * @param customerId      客户ID
     * @param pageNo          页码
     * @param pageSize        每页最大数量
     * @param startSearchTime 开始搜索时间
     * @return 客户的历史订单
     */
    @RequestMapping(value = "/history/orders", method = RequestMethod.GET)
    @ResponseBody
    public SaleOrderListResponse getHistoryOrders(
            @RequestParam("unitId") String unitId,
            @RequestParam("customerId") String customerId,
            @RequestParam("pageNo") int pageNo,
            @RequestParam("pageSize") int pageSize,
            @RequestParam("startSearchTime") String startSearchTime
    ) {
        SaleOrderListResponse response = new SaleOrderListResponse();
        List<SaleOrderListDTO> list = saleOrderService.getHistoryOrderList(unitId, customerId, startSearchTime, pageNo, pageSize);
        response.setSaleOrders(list);
        return response;
    }

    /**
     * 获取客户的历史订单
     *
     * @param unitId          组织ID
     * @param customerId      客户ID
     * @param pageNo          页码
     * @param pageSize        每页最大数量
     * @param startSearchTime 开始搜索时间
     * @return 客户的历史订单
     */
    @RequestMapping(value = "/history/style/orders", method = RequestMethod.GET)
    @ResponseBody
    public SaleOrderListResponse getHistoryStyleOrders(
            @RequestParam("unitId") String unitId,
            @RequestParam("customerId") String customerId,
            @RequestParam("styleId") String styleId,
            @RequestParam("pageNo") int pageNo,
            @RequestParam("pageSize") int pageSize,
            @RequestParam("startSearchTime") String startSearchTime
    ) {
        SaleOrderListResponse response = new SaleOrderListResponse();
        List<SaleOrderListDTO> list = saleOrderService.getHistoryStyleOrderList(unitId, customerId, startSearchTime, styleId, pageNo, pageSize);
        response.setSaleOrders(list);
        return response;
    }

    /**
     * 新增收/退款记录, 该接口还能添加备注和修改抹零金额
     *
     * @param request 新增收/退款记录请求参数
     * @return 新增收/退款记录回应结果
     */
    @RequestMapping(value = "/remittances", method = RequestMethod.POST)
    @ResponseBody
    public SaleOrderRemittanceCreateResponse addSaleOrderRemittances(
            @RequestBody SaleOrderRemittanceCreateRequest request
    ) {
        SaleOrderRemittanceCreateResponse response = new SaleOrderRemittanceCreateResponse();

        String saleOrderId = request.getSaleOrderId();
        BigDecimal oddment = request.getOddment();

        try {
            saleOrderService.doAtomically(() -> {
                saleOrderService.setOddment(saleOrderId, oddment);
                this.saveSaleOrderRemarks(request);
                this.saveSaleOrderRemittances(request);
                return null;
            });
        } catch (Exception e) {
            response.setCode(400);
            response.setMessage(e.getMessage());
            return response;
        }

        return response;
    }

    private void saveSaleOrderRemarks(SaleOrderRemittanceCreateRequest request) {
        List<SaleOrderRemarkDTO> remarks = request.getRemarks();
        if (remarks == null || remarks.isEmpty()) return;
        final int[] sequence = {0};
        remarks.forEach(dto -> {
            SaleOrderRemark remark = new SaleOrderRemark();
            remark.setId(Uid.random());
            remark.setCentralBillSaleOrderId(request.getSaleOrderId());
            remark.setRemark(dto.getRemark());
            remark.setDateRemarked(dto.getDateRemarked() == null ? new Date() : TimeKit.parse(dto.getDateRemarked(), "yyyy-MM-dd HH:mm:ss"));
            remark.setUserCreated(request.getUserId());
            remark.setSequence(sequence[0]++);
            saleOrderRemarkService.insertSelective(remark);
        });
    }

    private void saveSaleOrderRemittances(SaleOrderRemittanceCreateRequest request) {
        List<RemittanceDTO> remittances = request.getRemittances();
        if (remittances == null || remittances.isEmpty()) return;

        String dateRemitted = request.getDateRemitted();
        if (dateRemitted == null || dateRemitted.trim().isEmpty()) dateRemitted = TimeKit.format(new Date(), "yyyy-MM-dd");
        Date dateOperated = TimeKit.parse(dateRemitted, "yyyy-MM-dd");
        remittances.forEach(dto -> {
            Remittance remittance = new Remittance();
            remittance.setId(Uid.random());
            remittance.setBossUnitId(request.getBossUnitId());
            remittance.setRemitMethodId(dto.getRemitMethodId());
            remittance.setUnitId(request.getUnitId());
            remittance.setCustomerId(request.getCustomerId());
            remittance.setAmount(dto.getAmount());
            remittance.setOperatorId(request.getUserId());
            remittance.setSource(BillType.SALE_ORDER);
            remittance.setType(dto.getRemittanceType());
            remittance.setDateOperated(dateOperated);
            remittance.setUserCreated(request.getUserId());
            remittanceService.insertSelective(remittance);

            SaleOrderAction action = new SaleOrderAction();
            action.setId(Uid.random());
            action.setUserCreated(request.getUserId());
            action.setCentralBillSaleOrderId(request.getSaleOrderId());
            action.setActionType(ActionType.REMITTANCE);
            action.setActionId(remittance.getId());
            saleOrderActionService.insertSelective(action);
        });
    }

    /**
     * 更新销售单汇款记录
     *
     * @param request 更新销售单汇款记录请求
     * @return 更新销售单汇款记录结果
     */
    @RequestMapping(value = "/remittances", method = RequestMethod.PUT)
    @ResponseBody
    public SaleOrderRemittanceUpdateResponse updateSaleOrderRemittance(
            @RequestBody SaleOrderRemittanceUpdateRequest request
    ) {
        String dateRemitted = request.getDateRemitted();
        if (dateRemitted == null || dateRemitted.trim().isEmpty()) dateRemitted = TimeKit.format(new Date(), "yyyy-MM-dd");
        Date dateOperated = TimeKit.parse(dateRemitted, "yyyy-MM-dd");
        remittanceService.update(
                request.getRemittanceId(),
                request.getRemitMethodId(),
                request.getAmount(),
                dateOperated
        );
        return new SaleOrderRemittanceUpdateResponse();
    }

    @RequestMapping(value = "/clearances", method = RequestMethod.POST)
    @ResponseBody
    public SaleOrderClearanceCreateResponse addSaleOrderClearances(
            @RequestBody SaleOrderClearanceCreateRequest request
    ) {
        SaleOrderClearanceCreateResponse response = new SaleOrderClearanceCreateResponse();
        clearanceService.doAtomically(() -> {
            saveSaleOrderClearances(request);
            return null;
        });
        return response;
    }

    private void saveSaleOrderClearances(SaleOrderClearanceCreateRequest request) {
        List<ClearanceDTO> clearances = request.getClearances();
        if (clearances == null || clearances.isEmpty()) return;
        clearances.forEach(dto -> {
            Clearance clearance = new Clearance();
            clearance.setId(Uid.random());
            clearance.setBossUnitId(request.getBossUnitId());
            clearance.setUnitId(request.getUnitId());
            clearance.setClearanceType(dto.getClearanceType());
            clearance.setAmount(dto.getAmount());
            clearance.setUserCreated(request.getUserId());
            clearanceService.insertSelective(clearance);

            SaleOrderAction self = new SaleOrderAction();
            self.setId(Uid.random());
            self.setCentralBillSaleOrderId(request.getSaleOrderId());
            self.setActionType(dto.getClearanceType() == ClearanceType.IN ? ActionType.CLEARANCE_IN : ActionType.CLEARANCE_OUT);
            self.setActionId(clearance.getId());
            self.setUserCreated(request.getUserId());
            saleOrderActionService.insertSelective(self);

            SaleOrderAction that = new SaleOrderAction();
            that.setId(Uid.random());
            that.setCentralBillSaleOrderId(dto.getSaleOrderId());
            that.setActionType(dto.getClearanceType() == ClearanceType.IN ? ActionType.CLEARANCE_OUT : ActionType.CLEARANCE_IN);
            that.setActionId(clearance.getId());
            that.setUserCreated(request.getUserId());
            saleOrderActionService.insertSelective(that);
            //TODO 减结欠
        });
    }

    /**
     * 获取可核销的销售单记录列表
     *
     * @param request 可核销的销售单记录列表请求
     * @return 可核销的销售单记录列表结果
     */
    @RequestMapping(value = "/clearables", method = RequestMethod.GET)
    @ResponseBody
    public SaleOrderClearableListResponse clearables(SaleOrderClearableListRequest request) {
        SaleOrderClearableListResponse response = new SaleOrderClearableListResponse();
        List<SaleOrderQueryDTO> clearables = saleOrderService.clearables(
                request.getUnitId(),
                request.getCustomerId(),
                request.getStartSearchTime(),
                request.getPageNo(),
                request.getPageSize()
        );
        response.setOrders(clearables);
        return response;
    }


    /**
     * 更新销售单核销记录
     *
     * @param request 更新销售单核销记录请求
     * @return 更新销售单核销记录结果
     */
    @RequestMapping(value = "/clearances", method = RequestMethod.PUT)
    @ResponseBody
    public SaleOrderClearanceUpdateResponse updateSaleOrderClearance(
            @RequestBody SaleOrderClearanceUpdateRequest request
    ) {
        clearanceService.update(
                request.getClearanceId(),
                request.getAmount(),
                request.getUserId()
        );
        return new SaleOrderClearanceUpdateResponse();
    }

    /**
     * 创建收款单
     *
     * @param request
     * @return
     */
    @RequestMapping(value = "/receipt", method = RequestMethod.POST)
    @ResponseBody
    public ReceiptOrderCreateResponse createReceiptOrder(
            @RequestBody ReceiptOrderCreateRequest request
    ) {
        ReceiptOrderCreateResponse response = new ReceiptOrderCreateResponse();
        saleOrderService.doAtomically(() -> {
            saveReceiptOrder(request);
            return null;
        });
        return response;
    }

    /**
     * 保存收款单
     *
     * @param request
     * @return
     */
    private String saveReceiptOrder(ReceiptOrderCreateRequest request) {
        String unitID = request.getUnitId();
        String dateRemitted = request.getDateRemitted();
        if (request.getDateRemitted() == null) {
            Date now = new Date();
            dateRemitted = TimeKit.format(now, "yyyy-MM-dd");
        }
        Integer maxOrderNo = saleOrderService.getMaxOrderNoOfUnitIdAndOrderDay(unitID, dateRemitted);
        Integer orderNo = maxOrderNo == null ? 1 : maxOrderNo + 1;

        SaleOrder saleOrder = new SaleOrder();
        saleOrder.setId(Uid.random());
        saleOrder.setType(SaleOrderType.RECEIPT);
        saleOrder.setOrderDay(dateRemitted);
        saleOrder.setOrderNo(orderNo);
        saleOrder.setCustomerId(request.getCustomerId());
        saleOrder.setBossUnitId(request.getBossUnitId());
        saleOrder.setMerchandiserId(request.getUserId());
        saleOrder.setUnitId(request.getUnitId());
        saleOrder.setCanceled(false);
        saleOrder.setOddment(request.getOddment() != null ? request.getOddment() : BigDecimal.ZERO);
        saleOrder.setUserCreated(request.getUserId());
        saleOrder.setUserOrdered(request.getUserId());

        // 增加抹零操作记录
        SaleOrderAction action = new SaleOrderAction();
        action.setId(Uid.random());
        action.setUserCreated(request.getUserId());
        action.setCentralBillSaleOrderId(saleOrder.getId());
        action.setActionType(ActionType.ODDMENT);
        action.setActionId(saleOrder.getId());
        saleOrderActionService.insertSelective(action);

        saleOrderService.insertSelective(saleOrder);
        saveReceiptOrderRemittances(request, saleOrder.getId());
        saveReceiptOrderRemarks(request, saleOrder.getId());
        return saleOrder.getId();
    }

    /**
     * 创建收款单备注
     *
     * @param request
     * @param receiptOrderId
     */
    private void saveReceiptOrderRemarks(ReceiptOrderCreateRequest request, String receiptOrderId) {
        List<SaleOrderRemarkDTO> remarks = request.getRemarks();
        if (remarks == null || remarks.isEmpty()) return;
        final int[] sequence = {0};
        remarks.forEach(dto -> {
            SaleOrderRemark remark = new SaleOrderRemark();
            remark.setId(Uid.random());
            remark.setCentralBillSaleOrderId(receiptOrderId);
            remark.setRemark(dto.getRemark());
            remark.setDateRemarked(dto.getDateRemarked() == null ? new Date() : TimeKit.parse(dto.getDateRemarked(), "yyyy-MM-dd HH:mm:ss"));
            remark.setUserCreated(request.getUserId());
            remark.setSequence(sequence[0]++);
            saleOrderRemarkService.insertSelective(remark);
        });
    }

    /**
     * 创建收款动作记录
     *
     * @param request
     * @param receiptOrderId
     */
    private void saveReceiptOrderRemittances(ReceiptOrderCreateRequest request, String receiptOrderId) {
        List<RemittanceDTO> remittances = request.getRemittances();
        if (remittances == null || remittances.isEmpty()) return;

        String dateRemitted = request.getDateRemitted();
        if (dateRemitted == null || dateRemitted.trim().isEmpty()) dateRemitted = TimeKit.format(new Date(), "yyyy-MM-dd");
        Date dateOperated = TimeKit.parse(dateRemitted, "yyyy-MM-dd");
        remittances.forEach(dto -> {
            Remittance remittance = new Remittance();
            remittance.setId(Uid.random());
            remittance.setBossUnitId(request.getBossUnitId());
            remittance.setRemitMethodId(dto.getRemitMethodId());
            remittance.setUnitId(request.getUnitId());
            remittance.setCustomerId(request.getCustomerId());
            remittance.setAmount(dto.getAmount());
            remittance.setOperatorId(request.getUserId());
            remittance.setSource(BillType.RECEIPT_ORDER);
            remittance.setType(dto.getRemittanceType());
            remittance.setDateOperated(dateOperated);
            remittance.setUserCreated(request.getUserId());
            remittanceService.insertSelective(remittance);

            SaleOrderAction action = new SaleOrderAction();
            action.setId(Uid.random());
            action.setUserCreated(request.getUserId());
            action.setCentralBillSaleOrderId(receiptOrderId);
            action.setActionType(ActionType.REMITTANCE);
            action.setActionId(remittance.getId());
            saleOrderActionService.insertSelective(action);
        });
    }
}
